diff --git a/Makefile b/Makefile
index fe3c59afa..30ccf3f0c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,14 @@
SOURCE_DIR = src
FILES = \
+${SOURCE_DIR}/HEADER \
${SOURCE_DIR}/intro.js \
-${SOURCE_DIR}/backend.js \
+${SOURCE_DIR}/baseclasses.js \
${SOURCE_DIR}/commands.js \
-${SOURCE_DIR}/frontend.js \
+${SOURCE_DIR}/symbols.js \
+${SOURCE_DIR}/cursor.js \
+${SOURCE_DIR}/rootelements.js \
+${SOURCE_DIR}/publicapi.js \
${SOURCE_DIR}/outro.js
BUILD_DIR = ./build
diff --git a/publish.sh b/publish.sh
index 1755251d9..e2f35e8f1 100755
--- a/publish.sh
+++ b/publish.sh
@@ -9,7 +9,7 @@ cp mathquill.css build/mathquill.css
branch=`git branch | grep '\*' | sed 's/\* *//'`
git checkout gh-pages
rm mathquill.js mathquill.css
-cat build/mathquill.js | sed '34s://::' > mathquill.js
+cat build/mathquill.js | sed '12s://::' > mathquill.js
cp build/mathquill.css mathquill.css
rm build/mathquill.css
git commit -a -m "publish new mathquill.js"
diff --git a/src/HEADER b/src/HEADER
new file mode 100644
index 000000000..170843233
--- /dev/null
+++ b/src/HEADER
@@ -0,0 +1,5 @@
+/**
+ * Copyright 2010 Jay and Han (laughinghan@gmail.com)
+ * License, Usage and Readme at http://mathquill.com
+ */
+
\ No newline at end of file
diff --git a/src/backend.js b/src/baseclasses.js
similarity index 97%
rename from src/backend.js
rename to src/baseclasses.js
index 30044c0fa..a80e97d43 100644
--- a/src/backend.js
+++ b/src/baseclasses.js
@@ -1,6 +1,6 @@
-/**********************************************************
- * Back-end code: Core abstract classes and architecture.
- *********************************************************/
+/*************************************************
+ * Abstract base classes of blocks and commands.
+ ************************************************/
/**
* MathElement is the core Math DOM tree node prototype.
@@ -257,4 +257,3 @@ MathFragment.prototype = {
return newBlock;
}
};
-
diff --git a/src/cursor.js b/src/cursor.js
new file mode 100644
index 000000000..58c19121b
--- /dev/null
+++ b/src/cursor.js
@@ -0,0 +1,413 @@
+/********************************************
+ * Cursor and Selection "singleton" classes
+ *******************************************/
+
+/* The main thing that manipulates the Math DOM. Makes sure to manipulate the
+HTML DOM to match. */
+
+/* Sort of singletons, since there should only be one per editable math
+textbox, but any one HTML document can contain many such textboxes, so any one
+JS environment could actually contain many instances. */
+
+//A fake cursor in the fake textbox that the math is rendered in.
+function Cursor(root)
+{
+ this.parent = root;
+ var jQ = this.jQ = this._jQ = $('');
+
+ //API for the blinking cursor
+ function blink(){ jQ.toggleClass('blink'); }
+ var intervalId;
+ this.show = function()
+ {
+ this.jQ = this._jQ.removeClass('blink');
+ if(intervalId)
+ clearInterval(intervalId);
+ else if(this.parent.removeEmpty(this.jQ))
+ if(this.next)
+ if(this.selection && this.selection.prev === this.prev)
+ this.jQ.insertBefore(this.selection.jQ);
+ else
+ this.jQ.insertBefore(this.next.jQ);
+ else
+ this.jQ.appendTo(this.parent.jQ);
+ intervalId = setInterval(blink, 500);
+ return this;
+ };
+ this.hide = function()
+ {
+ if(intervalId)
+ clearInterval(intervalId);
+ intervalId = undefined;
+ this.jQ.detach();
+ this.jQ = $();
+ return this;
+ };
+}
+Cursor.prototype = {
+ prev: null,
+ next: null,
+ parent: null,
+ insertAt: function(parent, next, prev)
+ {
+ var p = this.parent;
+ this.parent = parent;
+ this.next = next;
+ this.prev = prev;
+ p.setEmpty(); //p.setEmpty may want to know where the cursor is going
+ },
+ insertBefore: function(el)
+ {
+ this.insertAt(el.parent, el, el.prev)
+ this.parent.jQ.addClass('hasCursor');
+ this.jQ.insertBefore(el.jQ.first());
+ return this;
+ },
+ insertAfter: function(el)
+ {
+ this.insertAt(el.parent, el.next, el);
+ this.parent.jQ.addClass('hasCursor');
+ this.jQ.insertAfter(el.jQ.last());
+ return this;
+ },
+ prependTo: function(el)
+ {
+ this.insertAt(el, el.firstChild, null);
+ if(el.removeEmpty(this.jQ))
+ if(el.parent)
+ this.jQ.prependTo(el.jQ);
+ else //only root has no parent
+ this.jQ.insertAfter(el.textarea);
+ return this;
+ },
+ appendTo: function(el)
+ {
+ this.insertAt(el, null, el.lastChild);
+ if(el.removeEmpty(this.jQ))
+ this.jQ.appendTo(el.jQ);
+ return this;
+ },
+ moveLeft: function()
+ {
+ if(this.selection)
+ this.insertBefore(this.selection.prev ? this.selection.prev.next : this.parent.firstChild).clearSelection();
+ else
+ if(this.prev)
+ if(this.prev.lastChild)
+ this.appendTo(this.prev.lastChild)
+ else
+ this.hopLeft();
+ else //we're at the beginning of a block
+ if(this.parent.prev)
+ this.appendTo(this.parent.prev);
+ else if(this.parent.parent)
+ this.insertBefore(this.parent.parent);
+ //otherwise we're at the beginning of the root, so do nothing.
+ return this.show();
+ },
+ moveRight: function()
+ {
+ if(this.selection)
+ this.insertAfter(this.selection.next ? this.selection.next.prev : this.parent.lastChild).clearSelection();
+ else
+ if(this.next)
+ if(this.next.firstChild)
+ this.prependTo(this.next.firstChild)
+ else
+ this.hopRight();
+ else //we're at the end of a block
+ if(this.parent.next)
+ this.prependTo(this.parent.next);
+ else if(this.parent.parent)
+ this.insertAfter(this.parent.parent);
+ //otherwise we're at the end of the root, so do nothing.
+ return this.show();
+ },
+ hopLeft: function()
+ {
+ this.jQ.insertBefore(this.prev.jQ.first());
+ this.next = this.prev;
+ this.prev = this.prev.prev;
+ return this;
+ },
+ hopRight: function()
+ {
+ this.jQ.insertAfter(this.next.jQ.last());
+ this.prev = this.next;
+ this.next = this.next.next;
+ return this;
+ },
+ write: function(ch)
+ {
+ if(this.selection)
+ {
+ //gotta do this before this.selection is mutated by 'new cmd(this.selection)'
+ this.prev = this.selection.prev;
+ this.next = this.selection.next;
+ }
+
+ var cmd;
+ if(ch.match(/[a-eg-zA-Z]/)) //exclude f because want florin
+ cmd = new Variable(ch);
+ else if(cmd = CharCmds[ch] || LatexCmds[ch])
+ cmd = new cmd(this.selection);
+ else
+ cmd = new VanillaSymbol(ch);
+
+ if(this.selection)
+ {
+ if(cmd instanceof Symbol)
+ this.selection.remove();
+ delete this.selection;
+ }
+
+ return this.insertNew(cmd);
+ },
+ insertNew: function(cmd)
+ {
+ cmd.parent = this.parent;
+ cmd.next = this.next;
+ cmd.prev = this.prev;
+ if(this.prev)
+ this.prev.next = cmd;
+ else
+ this.parent.firstChild = cmd;
+ if(this.next)
+ this.next.prev = cmd;
+ else
+ this.parent.lastChild = cmd;
+ cmd.jQ.insertBefore(this.jQ);
+
+ //adjust context-sensitive spacing
+ cmd.respace();
+ if(this.next)
+ this.next.respace();
+ if(this.prev)
+ this.prev.respace();
+
+ this.prev = cmd;
+
+ cmd.placeCursor(this);
+
+ this.jQ.change();
+
+ return this;
+ },
+ unwrapParent: function()
+ {
+ var gramp = this.parent.parent, greatgramp = gramp.parent,
+ cursor = this, prev = gramp.prev;
+
+ gramp.eachChild(function()
+ {
+ if(this.isEmpty())
+ return;
+
+ this.eachChild(function()
+ {
+ this.parent = greatgramp;
+ this.jQ.insertBefore(gramp.jQ);
+ });
+ this.firstChild.prev = prev;
+ if(prev)
+ prev.next = this.firstChild;
+ else
+ this.firstChild.parent.firstChild = this.firstChild;
+
+ prev = this.lastChild;
+ });
+ prev.next = gramp.next;
+ if(prev.next)
+ prev.next.prev = prev;
+ else
+ greatgramp.lastChild = prev;
+
+ if(!this.next)
+ if(this.prev)
+ this.next = this.prev.next;
+ else
+ while(!this.next)
+ if(this.parent = this.parent.next)
+ this.next = this.parent.lastChild;
+ else
+ {
+ this.next = gramp.next;
+ this.parent = greatgramp;
+ break;
+ }
+
+ if(this.next)
+ this.insertBefore(this.next);
+ else
+ this.appendTo(greatgramp);
+
+ gramp.jQ.remove();
+
+ if(gramp.prev)
+ gramp.prev.respace();
+ if(gramp.next)
+ gramp.next.respace();
+ },
+ backspace: function()
+ {
+ if(this.deleteSelection());
+ else if(this.prev)
+ if(this.prev.isEmpty())
+ this.prev = this.prev.remove().prev;
+ else
+ this.selectLeft();
+ else if(this.parent.parent)
+ if(this.parent.parent.isEmpty())
+ return this.insertAfter(this.parent.parent).backspace();
+ else
+ this.unwrapParent();
+
+ if(this.prev)
+ this.prev.respace();
+ if(this.next)
+ this.next.respace();
+ this.jQ.change();
+
+ return this;
+ },
+ deleteForward: function()
+ {
+ if(this.deleteSelection());
+ else if(this.next)
+ if(this.next.isEmpty())
+ this.next = this.next.remove().next;
+ else
+ this.selectRight();
+ else if(this.parent.parent)
+ if(this.parent.parent.isEmpty())
+ return this.insertBefore(this.parent.parent).deleteForward();
+ else
+ this.unwrapParent();
+
+ if(this.prev)
+ this.prev.respace();
+ if(this.next)
+ this.next.respace();
+ this.jQ.change();
+
+ return this;
+ },
+ selectLeft: function()
+ {
+ if(this.selection)
+ if(this.selection.prev === this.prev) //if cursor is at left edge of selection,
+ {
+ if(this.prev) //then extend left if possible
+ {
+ this.hopLeft().next.jQ.prependTo(this.selection.jQ);
+ this.selection.prev = this.prev;
+ }
+ else if(this.parent.parent) //else level up if possible
+ this.insertBefore(this.parent.parent).selection.levelUp();
+ }
+ else //else cursor is at right edge of selection, retract left
+ {
+ this.prev.jQ.insertAfter(this.selection.jQ);
+ this.hopLeft().selection.next = this.next;
+ if(this.selection.prev === this.prev)
+ this.deleteSelection();
+ }
+ else
+ if(this.prev)
+ this.hide().hopLeft().selection = new Selection(this.parent, this.prev, this.next.next);
+ else //end of a block
+ if(this.parent.parent)
+ this.hide().insertBefore(this.parent.parent).selection = new Selection(this.parent, this.prev, this.next.next);
+ },
+ selectRight: function()
+ {
+ if(this.selection)
+ if(this.selection.next === this.next) //if cursor is at right edge of selection,
+ {
+ if(this.next) //then extend right if possible
+ {
+ this.hopRight().prev.jQ.appendTo(this.selection.jQ);
+ this.selection.next = this.next;
+ }
+ else if(this.parent.parent) //else level up if possible
+ this.insertAfter(this.parent.parent).selection.levelUp();
+ }
+ else //else cursor is at left edge of selection, retract right
+ {
+ this.next.jQ.insertBefore(this.selection.jQ);
+ this.hopRight().selection.prev = this.prev;
+ if(this.selection.next === this.next)
+ this.deleteSelection();
+ }
+ else
+ if(this.next)
+ this.hide().hopRight().selection = new Selection(this.parent, this.prev.prev, this.next);
+ else //end of a block
+ if(this.parent.parent)
+ this.hide().insertAfter(this.parent.parent).selection = new Selection(this.parent, this.prev.prev, this.next);
+ },
+ clearSelection: function()
+ {
+ if(this.show().selection)
+ {
+ this.selection.clear();
+ delete this.selection;
+ }
+ return this;
+ },
+ deleteSelection: function()
+ {
+ if(this.show().selection)
+ {
+ this.prev = this.selection.prev;
+ this.next = this.selection.next;
+ this.selection.remove();
+ delete this.selection;
+ return true;
+ }
+ else
+ return false;
+ }
+}
+
+function Selection(parent, prev, next)
+{
+ MathFragment.apply(this, arguments);
+}
+Selection.prototype = $.extend(new MathFragment, {
+ jQinit: function(children)
+ {
+ return this.jQ = children.wrapAll('').parent();
+ //wrapAll clones, so can't do .wrapAll(this.jQ = $(...));
+ },
+ levelUp: function()
+ {
+ this.clear().jQinit(this.parent.parent.jQ);
+
+ this.prev = this.parent.parent.prev;
+ this.next = this.parent.parent.next;
+ this.parent = this.parent.parent.parent;
+
+ return this;
+ },
+ clear: function()
+ {
+ this.jQ.replaceWith(this.jQ.children());
+ return this;
+ },
+ blockify: function()
+ {
+ this.jQ.replaceWith(this.jQ = this.jQ.children());
+ return MathFragment.prototype.blockify.call(this);
+ },
+ detach: function()
+ {
+ var block = MathFragment.prototype.blockify.call(this);
+ this.blockify = function()
+ {
+ this.jQ.replaceWith(block.jQ = this.jQ = this.jQ.children());
+ return block;
+ };
+ return this;
+ }
+});
diff --git a/src/intro.js b/src/intro.js
index 3294512de..3a76d0a5a 100644
--- a/src/intro.js
+++ b/src/intro.js
@@ -1,37 +1,11 @@
-/**
-* Usage:
-*
-* Wherever you'd like to have an editable math textbox:
+/****************************
+ * Important opening stuff.
+ ***************************/
-
+(function($){ //takes in the jQuery function as an argument
-* or to convert LaTeX math to HTML:
-
- \frac{d}{dx}\sqrt{x}
-
-* Note that for dynamically created elements, you will need to call our
-* jQuery plugin after inserting into the visible HTML DOM:
-
- $('\sqrt{e^x}').appendTo('body').mathquill() or .mathquill('editable')
-
-* If it's necessary to call the plugin before inserting into the visible DOM,
-* you can redraw once it is visible:
-
- $('a_n x^n').mathquill().appendTo('body').mathquill('redraw');
-
-* (Do be warned that will trigger a flurry of change events.)
-*
-* Any element that has been MathQuill-ified can be reverted:
-
- $('.mathquill-embedded-latex').mathquill('revert');
-
-*
-*/
-
-jQuery.fn.mathquill = (function($){ //takes in the jQuery function as an argument
-
-//Note: if the following is no longer on line 34, please modify publish.sh accordingly
+//Note: if the following is no longer on line 12 of build/mathquill.js, please modify publish.sh accordingly
//$('head').prepend('');
-var todo = function(){ alert('BLAM!\n\nAHHHHHH!\n\n"Oh god, oh god, I\'ve never seen so much blood!"\n\nYeah, that doesn\'t fully work yet.'); };
+function todo(){ alert('BLAM!\n\nAHHHHHH!\n\n"Oh god, oh god, I\'ve never seen so much blood!"\n\nYeah, that doesn\'t fully work yet.'); };
diff --git a/src/outro.js b/src/outro.js
index 373ede4c9..ab58db9c0 100644
--- a/src/outro.js
+++ b/src/outro.js
@@ -1,11 +1,2 @@
-//on document ready, transmogrify all and
-// elements to mathquill elements.
-$(function()
-{
- $('.mathquill-embedded-latex').mathquill();
- $('.mathquill-editable').mathquill('editable');
- $('.mathquill-textbox').mathquill('textbox');
-});
-return mathquill;
-}(jQuery));
\ No newline at end of file
+}(jQuery));
diff --git a/src/publicapi.js b/src/publicapi.js
new file mode 100644
index 000000000..07926c098
--- /dev/null
+++ b/src/publicapi.js
@@ -0,0 +1,46 @@
+/*********************************************************
+ * The actual jQuery plugin and document ready handlers.
+ ********************************************************/
+
+//The publicy exposed method of jQuery.prototype, available (and meant to be
+//called) on jQuery-wrapped HTML DOM elements.
+$.fn.mathquill = function(cmd, latex)
+{
+ switch(cmd)
+ {
+ case 'html':
+ return this.html().replace(/<\/span>|