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>|') + .prependTo(jQ.addClass('mathquill-editable')).children(); + if(textbox) + jQ.addClass('mathquill-textbox'); - gramp.eachChild(function() + textarea.focus(function(e) { - 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; + if(!cursor.parent) + cursor.appendTo(root); + cursor.parent.jQ.addClass('hasCursor'); + if(cursor.selection) + cursor.selection.jQ.removeClass('blur'); else - this.firstChild.parent.firstChild = this.firstChild; - - prev = this.lastChild; + cursor.show(); + e.stopPropagation(); + } + ).blur(function(e) + { + cursor.hide().parent.setEmpty(); + if(cursor.selection) + cursor.selection.jQ.addClass('blur'); + e.stopPropagation(); }); - 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(); + var lastKeydnEvt; //see Wiki page "Keyboard Events" + jQ.bind('keydown.mathquill',function(e) //see Wiki page "Keyboard Events" + { + lastKeydnEvt = e; + e.happened = true; + return e.returnValue = cursor.parent.keydown(e) || + (e.stopImmediatePropagation(), false); + } + ).bind('keypress.mathquill',function(e) + { + //on auto-repeated key events, keypress may get triggered but not keydown + // (see Wiki page "Keyboard Events") + if(lastKeydnEvt.happened) + lastKeydnEvt.happened = false; 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 + lastKeydnEvt.returnValue = cursor.parent.keydown(lastKeydnEvt); + //only call keypress if keydown returned true + return lastKeydnEvt.returnValue && (e.ctrlKey || e.metaKey || e.which < 32 || + cursor.parent.keypress(e) || (e.stopImmediatePropagation(), false)); + } + ).bind('click.mathquill',function(e) + { + var clicked = $(e.target); + if(clicked.hasClass('empty')) { - this.prev.jQ.insertAfter(this.selection.jQ); - this.hopLeft().selection.next = this.next; - if(this.selection.prev === this.prev) - this.deleteSelection(); + cursor.clearSelection().prependTo(clicked.data('[[mathquill internal data]]').block); + return false; } - 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, + + var cmd = clicked.data('[[mathquill internal data]]'); + if(cmd) { - if(this.next) //then extend right if possible + if(cmd.cmd && !cmd.block) { - this.hopRight().prev.jQ.appendTo(this.selection.jQ); - this.selection.next = this.next; + cursor.clearSelection(); + if(clicked.outerWidth() > 2*(e.pageX - clicked.offset().left)) + cursor.insertBefore(cmd.cmd); + else + cursor.insertAfter(cmd.cmd); + return false; } - 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 + else if(!(cmd = (clicked = clicked.parent()).data('[[mathquill internal data]]'))) + return; + + cursor.clearSelection(); + if(cmd.cmd) + cursor.insertAfter(cmd.cmd); + else + cursor.appendTo(cmd.block); + //move cursor to position closest to click + var prevPrevDist, prevDist, dist = cursor.jQ.offset().left - e.pageX; + do { - this.next.jQ.insertBefore(this.selection.jQ); - this.hopRight().selection.prev = this.prev; - if(this.selection.next === this.next) - this.deleteSelection(); + cursor.moveLeft(); + prevPrevDist = prevDist; + prevDist = dist; + dist = Math.abs(cursor.jQ.offset().left - e.pageX); } - 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) + while(dist <= prevDist && dist != prevPrevDist); + if(dist != prevPrevDist) + cursor.moveRight(); + + return false; + } + ).bind('click.mathquill',function() { - this.selection.clear(); - delete this.selection; + textarea.focus(); } - return this; - }, - deleteSelection: function() - { - if(this.show().selection) + ).bind('focus.mathquill blur.mathquill',function(e) { - this.prev = this.selection.prev; - this.next = this.selection.next; - this.selection.remove(); - delete this.selection; - return true; + textarea.trigger(e); } - else - return false; - } -} - -function Selection(parent, prev, next) -{ - MathFragment.apply(this, arguments); + ).blur(); + }); } -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; - } -}); function RootMathBlock(){} RootMathBlock.prototype = $.extend(new MathBlock, { @@ -699,167 +424,3 @@ RootTextBlock.prototype = $.extend(new MathBlock, { return false; } }); - -//The actual, publicly exposed method of jQuery.prototype, available -//(and meant to be called) on jQuery-wrapped HTML DOM elements. -function mathquill() -{ - if(arguments[0] === 'html') - return this.html().replace(/<\/span>|') - .prependTo(jQ.addClass('mathquill-editable')).children(); - if(textbox) - jQ.addClass('mathquill-textbox'); - - textarea.focus(function(e) - { - if(!cursor.parent) - cursor.appendTo(root); - cursor.parent.jQ.addClass('hasCursor'); - if(cursor.selection) - cursor.selection.jQ.removeClass('blur'); - else - cursor.show(); - e.stopPropagation(); - } - ).blur(function(e) - { - cursor.hide().parent.setEmpty(); - if(cursor.selection) - cursor.selection.jQ.addClass('blur'); - e.stopPropagation(); - }); - - var lastKeydnEvt; //see Wiki page "Keyboard Events" - jQ.bind('focus.mathquill blur.mathquill',function(e) - { - textarea.trigger(e); - } - ).bind('click.mathquill',function(e) - { - var clicked = $(e.target); - if(clicked.hasClass('empty')) - { - cursor.clearSelection().prependTo(clicked.data('[[mathquill internal data]]').block); - return false; - } - - var cmd = clicked.data('[[mathquill internal data]]'); - if(cmd) - { - if(cmd.cmd && !cmd.block) - { - cursor.clearSelection(); - if(clicked.outerWidth() > 2*(e.pageX - clicked.offset().left)) - cursor.insertBefore(cmd.cmd); - else - cursor.insertAfter(cmd.cmd); - return false; - } - } - else if(!(cmd = (clicked = clicked.parent()).data('[[mathquill internal data]]'))) - return; - - cursor.clearSelection(); - if(cmd.cmd) - cursor.insertAfter(cmd.cmd); - else - cursor.appendTo(cmd.block); - //move cursor to position closest to click - var prevPrevDist, prevDist, dist = cursor.jQ.offset().left - e.pageX; - do - { - cursor.moveLeft(); - prevPrevDist = prevDist; - prevDist = dist; - dist = Math.abs(cursor.jQ.offset().left - e.pageX); - } - while(dist <= prevDist && dist != prevPrevDist); - if(dist != prevPrevDist) - cursor.moveRight(); - - return false; - } - ).bind('click.mathquill',function() - { - textarea.focus(); - } - ).bind('keydown.mathquill',function(e) //see Wiki page "Keyboard Events" - { - lastKeydnEvt = e; - e.happened = true; - return e.returnValue = cursor.parent.keydown(e) || - (e.stopImmediatePropagation(), false); - } - ).bind('keypress.mathquill',function(e) - { - //on auto-repeated key events, keypress may get triggered but not keydown - // (see Wiki page "Keyboard Events") - if(lastKeydnEvt.happened) - lastKeydnEvt.happened = false; - else - lastKeydnEvt.returnValue = cursor.parent.keydown(lastKeydnEvt); - //only call keypress if keydown returned true - return lastKeydnEvt.returnValue && (e.ctrlKey || e.metaKey || e.which < 32 || - cursor.parent.keypress(e) || (e.stopImmediatePropagation(), false)); - } - ).blur(); - }); - - return this; -}; - diff --git a/src/symbols.js b/src/symbols.js new file mode 100644 index 000000000..e69de29bb