<!DOCTYPE html> <html> <head> <title>Dynamic Preview of Textarea with MathJax Content</title> <!– Copyright © 2012-2018 The MathJax Consortium –> <meta http-equiv=“Content-Type” content=“text/html; charset=UTF-8” /> <meta http-equiv=“X-UA-Compatible” content=“IE=edge” />

<style> .changed { color: red } </style>

<script type=“text/x-mathjax-config”>

MathJax.Hub.Config({
  TeX: {
    equationNumbers: {autoNumber: "AMS"},
    extensions: ["begingroup.js"]
  },
  showProcessingMessages: false,
  tex2jax: { inlineMath: [['$','$'],['\\(','\\)']] }
});

</script> <script type=“text/javascript” src=“../MathJax.js?config=TeX-AMS-MML_HTMLorMML”></script>

<script> var Preview = {

typeset: null,     // the typeset preview area (filled in by Init below)
preview: null,     // the untypeset preview    (filled in by Init below)
buffer: null,      // the new preview to be typeset (filled in by Init below)
numbers: [],       // the equation numbers per paragraph
labels: [],        // the equation labels per paragraph
defs: [],          // the definitions per paragraph

oldtext: '',       // used to see if an update is needed
pending: false,    // true when a restart is in the MathJax queue

colorDelay: 400,   // how long to leave changed paragraphs colored
timeout: null,     // timeout for changed style remover
labelDelay: 1250,  // how long to wait before reprocessing for label changes
ltimeout: null,    // timeout for changed labels

//
//  Get the preview and buffer DIV's
//
Init: function () {
  this.typeset = document.getElementById("MathPreview");
  this.buffer = document.createElement("div");
  this.preview = document.createElement("div");
},

//
//  This gets called when a key is pressed in the textarea.
//
Update: function () {
  var text = document.getElementById("MathInput").value;
  text = text.replace(/^\s+/,'').replace(/\s+$/,'');
  if (text !== this.oldtext) {
    this.oldtext = text;
    if (!this.pending) {
      this.pending = true;
      MathJax.Hub.Queue(["Restart",this]);
    }
  }
},

Restart: function (clear,n) {
  this.pending = false; this.incremental = true;
  var text = this.oldtext.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  var text = "<p>"+text.replace(/\n\n+/g,"</p><p>")+"</p>";

// var text = text.replace(/nn+/g,“<p>”);

  this.buffer.innerHTML = text;
  if (this.timeout) {clearTimeout(this.timeout); this.timeout = null}
  if (this.ltimeout) {clearTimeout(this.ltimeout); this.ltimeout = null}
  if (clear) {
    this.preview.innerHTML = this.typeset.innerHTML = "";
    this.numbers = []; this.labels = []; this.defs = [];
    MathJax.Hub.Queue(["resetEquationNumbers",MathJax.InputJax.TeX]);
  }
  if (n != null) {
    this.labels.splice(n); this.numbers.splice(n);
    while (this.preview.childNodes.length > n) {
      this.preview.removeChild(this.preview.childNodes(n));
      this.typeset.removeChild(this.typeset.childNodes(n));
    }
  }
  var update = this.CompareBuffers();
  if (update.length) {
    MathJax.Hub.Queue(
      ["Typeset",MathJax.Hub,update],
      ["PostTypeset",this,update,clear]
    );
  }
},

CompareBuffers: function () {
  var i, t1, t2, update = [], n = 0;
  var b1 = this.buffer.childNodes,
      b2 = this.preview.childNodes,
      b3 = this.typeset.childNodes;
  var m1 = b1.length, m2 = b2.length, m = Math.min(m1,m2);
  //
  //  Make sure all top-level elements are containers
  //
  for (i = 0; i < m1; i++) {
    t1 = b1[i];
    if (typeof(t1.innerHTML) === "undefined") {
      this.buffer.replaceChild(document.createElement("span"),t1);
      b1[i].appendChild(t1);
    }
  }
  //
  //  Find first non-matching element,
  //   and determine the starting equation number
  //
  for (i = 0; i < m; i++) {
    this.removeChanged(b3[i]);
    t1 = b1[i].innerHTML, t2 = b2[i].innerHTML;
    if (t1 !== t2) break;
    n += this.numbers[i];
  }
  //
  //  Rest the equation numbers to the starting value
  //
  MathJax.InputJax.TeX.resetEquationNumbers(n,true);
  this.i = i; this.changed = null;
  this.refs = []; this.defs = [];

  //
  //  Find last non-matching element
  //
  while (m1 > i && m2 > i) {
    t1 = b1[--m1].innerHTML, t2 = b2[--m2].innerHTML;
    this.removeChanged(b3[m2]);
    if (t1 !== t2) break;
  }
  //
  //  Remove differing elements from typeset copy
  //  and add in the new (untypeset) elements.
  //
  var tail = b3[m2+1];
  this.recordNumbers(this.numbers.splice(i,m2+1-i),n);
  this.recordLabels(this.labels.splice(i,m2+1-i));
  while (m2 >= i && b3[i]) {this.typeset.removeChild(b3[i]); m2--}
  while (i <= m1 && b1[i]) {
    this.numbers.splice(i,0,0); this.labels.splice(i,0,[]);
    var node = b1[i].cloneNode(true); update.push(node);
    this.typeset.insertBefore(node,tail); i++;
    if (node.className && node.className != "")
      {node.className += " changed"} else {node.className = "changed"}
  }
  //
  //  Swap buffers and set up the new buffer for the next change
  //
  this.preview = this.buffer; this.buffer = document.createElement("div");
  return update;
},

recordNumbers: function (numbers,top) {
  this.oldtop = this.newtop = top;
  for (var i = 0, m = numbers.length; i < m; i++) {this.oldtop += numbers[i]}
},

recordLabels: function (labels) {
  var AMS = MathJax.Extension["TeX/AMSmath"];
  this.oldlabels = labels.join(','); this.newlabels = [];
  if (labels && labels.length) {
    for (var i = 0, m = labels.length; i < m; i++) {
      for (var j = 0, n = labels[i].length; j < n; j++) {
        delete AMS.labels[labels[i][j].split(/=/)[0]];
      }
    }
  }
},

//
//  Remove the "changed" class from an element (leaving all other classes)
//
removeChanged: function (node) {
  if (node.className) {
    node.className = node.className.toString()
                         .replace(/(^|\s+)changed(\s|$)/,"$2")
                         .replace(/^\s+/,"");
  }
},

PostTypeset: function (nodes,clear) {
  var incremental = this.incremental; this.incremental = false;
  if (incremental && this.refs.length) {
    var refs = this.refs; this.refs = [];
    var queue = MathJax.Callback.Queue(["Reprocess",MathJax.Hub,refs,{}]);
    return queue.Push(["PostTypeset",this,nodes,clear]);
  }
  this.changed = nodes; this.timeout = setTimeout(this.Unmark,this.colorDelay);
  if (clear) return;
  if (nodes.length !== this.preview.childNodes.length) {
    //  ###  Make delay be dynamic based on number of equations?  ###
    if (this.newlabels && this.newlabels.join(',') !== this.oldlabels) {
      this.ltimeout = setTimeout(this.Relabel,this.labelDelay);
    } else if (this.newtop != this.oldtop) {
      this.ltimeout = setTimeout(this.Renumber,this.labelDelay);
    }
  }
},
Unmark: function () {
  var nodes = Preview.changed; Preview.changed = Preview.timeout = null;
  for (var i = 0, m = nodes.length; i < m; i++) {Preview.removeChanged(nodes[i])}
},
Relabel: function () {
  this.pending = true;
  MathJax.Hub.Queue(["Restart",Preview,true]);
},
Renumber: function () {
  if (Preview.i < Preview.preview.childNodes.length) {
    this.pending = true;
    MathJax.Hub.Queue(["Restart",Preview,false,Preview.i]);
  }
}

};

MathJax.Hub.Register.StartupHook(“TeX Jax Ready”,function () {

MathJax.InputJax.TeX.postfilterHooks.Add(function (data) {
  if (Preview.incremental) {
    var AMS = MathJax.Extension["TeX/AMSmath"];
    var labels = Preview.labels[Preview.i];
    for (var id in AMS.eqlabels) {if (AMS.eqlabels.hasOwnProperty(id)) {
      labels.push(id+"="+AMS.eqlabels[id])
    }}
    Preview.newlabels = Preview.newlabels.concat(labels);
  }
});

}); MathJax.Hub.Register.MessageHook(“End Math”,function (message) {

if (Preview.incremental) {
  var AMS = MathJax.Extension["TeX/AMSmath"];
  Preview.numbers[Preview.i++] = AMS.startNumber - Preview.newtop;
  Preview.newtop = AMS.startNumber;
}

}); MathJax.Hub.Register.MessageHook(“End Math Input”,function () {

if (Preview.incremental) {
  var AMS = MathJax.Extension["TeX/AMSmath"];
  Preview.refs = Preview.refs.concat(AMS.refs);
  AMS.refs = [];
}

},5); // priority = 5 to make sure it is before AMS runs.

MathJax.Hub.Register.StartupHook(“TeX begingroup Ready”,function () {

var STACK = MathJax.InputJax.TeX.eqnStack;
var DEF = STACK.Def;
STACK.Def = function () {
  if (Preview.incremental) {Preview.defs.push([].slice.call(arguments,0))}
  DEF.apply(this,arguments);
}

});

</script> </head> <body>

Type text with embedded TeX in the box below:<br/>

<textarea id=“MathInput” cols=“60” rows=“10” onkeyup=“Preview.Update()” onkeydown=“Preview.Update()” style=“margin-top:5px”> </textarea> <br/><br/> Preview is shown here: <div id=“MathPreview” style=“border:1px solid; padding: 3px; width:50%; margin-top:5px”></div>

<script> Preview.Init(); </script>

</body> </html>