You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
10 KiB
302 lines
10 KiB
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Dynamic Preview of Textarea with MathJax Content</title>
|
|
<!-- Copyright (c) 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"],
|
|
noErrors: {disabled: true}
|
|
},
|
|
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)
|
|
data: [], // paragraph-specific data
|
|
|
|
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
|
|
ctimeout: 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 (from) {
|
|
this.pending = false;
|
|
var text = this.oldtext.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
// var text = "<p>"+text.replace(/\n\n+/g,"</p><p>")+"</p>";
|
|
var text = text.replace(/\n\n+/g,"<p>");
|
|
this.buffer.innerHTML = text;
|
|
if (this.ctimeout) {clearTimeout(this.ctimeout); this.ctimeout = null}
|
|
if (this.ltimeout) {clearTimeout(this.ltimeout); this.ltimeout = null}
|
|
var update = this.CompareBuffers(from);
|
|
if (update.needed) {
|
|
MathJax.Hub.Queue(
|
|
["PreTypeset",this,update],
|
|
["Typeset",this,update],
|
|
["PostTypeset",this,update]
|
|
);
|
|
}
|
|
},
|
|
|
|
CompareBuffers: function (from) {
|
|
var b1 = this.buffer.childNodes,
|
|
b2 = this.preview.childNodes,
|
|
i, m1 = b1.length, m2 = b2.length;
|
|
//
|
|
// Make sure all top-level elements are containers
|
|
//
|
|
for (i = 0; i < m1; i++) {
|
|
var node = b1[i];
|
|
if (typeof(node.innerHTML) === "undefined") {
|
|
this.buffer.replaceChild(document.createElement("span"),node);
|
|
b1[i].appendChild(node);
|
|
}
|
|
}
|
|
//
|
|
// Determine the range of elements to update
|
|
//
|
|
if (from != null) {
|
|
//
|
|
// If from a starting point to the end, return the proper range
|
|
//
|
|
i = from; m1--; m2--;
|
|
} else {
|
|
//
|
|
// Find first non-matching element, if any,
|
|
// and the last non-matching element
|
|
//
|
|
m = Math.min(m1,m2);
|
|
for (i = 0; i < m; i++) {if (b1[i].innerHTML !== b2[i].innerHTML) break}
|
|
if (i === m && m1 === m2) {return {needed: false}}
|
|
while (m1 > i && m2 > i) {if (b1[--m1].innerHTML !== b2[--m2].innerHTML) break}
|
|
}
|
|
return {needed:true, start:i, end1:m1, end2:m2};
|
|
},
|
|
|
|
Typeset: function (update) {
|
|
return MathJax.Hub.Typeset(update.nodes);
|
|
},
|
|
|
|
PreTypeset: function (update) {
|
|
var TEX = MathJax.InputJax.TeX;
|
|
var i, m, n = 0, defs = [], m1 = update.end1, m2 = update.end2;
|
|
var b1 = this.buffer.childNodes,
|
|
b2 = this.typeset.childNodes;
|
|
//
|
|
// Remove the change color, if any
|
|
//
|
|
if (this.changed) {this.Unmark()}
|
|
|
|
//
|
|
// Determine the starting equation number
|
|
//
|
|
for (i = 0, m = update.start; i < m; i++) {
|
|
n += this.data[i].number;
|
|
defs = defs.concat(this.data[i].defs);
|
|
}
|
|
TEX.resetEquationNumbers(n,true);
|
|
//
|
|
// Pop any left over \begingroups and push a new one
|
|
// Then define any macros from previous paragraphs
|
|
//
|
|
while (TEX.rootStack.top > 1) {TEX.rootStack.stack.pop(); TEX.rootStack.top--}
|
|
TEX.rootStack.Push(TEX.nsStack.nsFrame());
|
|
for (i = 0, m = defs.length; i < m; i++) {TEX.rootStack.Def.apply(TEX.rootStack,defs[i])}
|
|
i = this.i = update.start; this.refs = [];
|
|
|
|
//
|
|
// Remove differing elements from typeset copy
|
|
// and add in the new (untypeset) elements.
|
|
//
|
|
this.recordOldData(this.data.splice(i,m2+1-i),n);
|
|
var tail = b2[m2+1]; update.nodes = [];
|
|
while (m2 >= i && b2[i]) {this.typeset.removeChild(b2[i]); m2--}
|
|
while (i <= m1 && b1[i]) {
|
|
this.data.splice(i,0,{number:0, labels:[], defs:[]});
|
|
var node = b1[i].cloneNode(true); update.nodes.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");
|
|
this.incremental = true;
|
|
},
|
|
|
|
recordOldData: function (data,top) {
|
|
var AMS = MathJax.Extension["TeX/AMSmath"];
|
|
var labels = [], defs = [];
|
|
this.oldtop = this.newtop = top;
|
|
for (var i = 0, m = data.length; i < m; i++) {
|
|
this.oldtop += data[i].number;
|
|
defs.push(data[i].defs.all);
|
|
for (var j = 0, n = data[i].labels.length; j < n; j++) {
|
|
delete AMS.labels[data[i].labels[j].split(/=/)[0]];
|
|
labels.push(data[i].labels[j]);
|
|
}
|
|
}
|
|
this.oldlabels = labels.join(''); this.newlabels = [];
|
|
this.olddefs = defs.join(''); this.newdefs = [];
|
|
},
|
|
|
|
PostTypeset: function (update) {
|
|
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,update]);
|
|
}
|
|
this.changed = update.nodes; this.ctimeout = setTimeout(this.Unmark,this.colorDelay);
|
|
if (update.nodes.length !== this.preview.childNodes.length) {
|
|
// ### Make delay be dynamic based on number of equations? ###
|
|
if (this.needsRefresh || this.newlabels && this.newlabels.join('') !== this.oldlabels) {
|
|
this.needsRefresh = true;
|
|
this.ltimeout = setTimeout(this.Refresh,this.labelDelay);
|
|
} else {
|
|
if (this.newtop != this.oldtop || this.newdefs.join('') !== this.olddefs) {
|
|
if (this.needsRenumber == null) {this.needsRenumber = this.i}
|
|
else {this.needsRenumber = Math.min(this.needsRenumber,this.i)}
|
|
}
|
|
if (this.needsRenumber != null)
|
|
{this.ltimeout = setTimeout(this.Renumber,this.labelDelay)}
|
|
}
|
|
}
|
|
},
|
|
Unmark: function () {
|
|
var nodes = Preview.changed; Preview.changed = Preview.ctimeout = null;
|
|
for (var i = 0, m = nodes.length; i < m; i++) {Preview.removeChanged(nodes[i])}
|
|
},
|
|
Refresh: function () {
|
|
Preview.pending = true; Preview.needsRefresh = false; delete Preview.needsRenumber;
|
|
MathJax.Hub.Queue(["Restart",Preview,0]);
|
|
},
|
|
Renumber: function () {
|
|
if (Preview.needsRenumber < Preview.preview.childNodes.length) {
|
|
var n = Preview.needsRenumber;
|
|
Preview.pending = true; delete Preview.needsRenumber;
|
|
MathJax.Hub.Queue(["Restart",Preview,n]);
|
|
}
|
|
},
|
|
|
|
//
|
|
// 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+/,"");
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
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.data[Preview.i].labels;
|
|
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("Begin Math Input",function () {
|
|
if (Preview.incremental) {Preview.eqDefs = []; Preview.eqDefs.all = []}
|
|
});
|
|
MathJax.Hub.Register.MessageHook("End Math Input",function () {
|
|
if (Preview.incremental) {
|
|
var AMS = MathJax.Extension["TeX/AMSmath"];
|
|
var data = Preview.data[Preview.i];
|
|
Preview.refs = Preview.refs.concat(AMS.refs); AMS.refs = [];
|
|
Preview.eqDefs.all = Preview.eqDefs.all.join("");
|
|
Preview.newdefs.push(Preview.eqDefs.all);
|
|
data.defs = Preview.eqDefs;
|
|
data.number = AMS.startNumber - Preview.newtop;
|
|
Preview.newtop = AMS.startNumber;
|
|
Preview.i++;
|
|
}
|
|
},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.eqDefs.push([].slice.call(arguments,0));
|
|
Preview.eqDefs.all.push(arguments[0]+"{"+arguments[1]+"}");
|
|
}
|
|
DEF.apply(this,arguments);
|
|
}
|
|
//
|
|
// Temporary hack to fix typo in begingroup.js
|
|
//
|
|
MathJax.InputJax.TeX.rootStack.stack[0].environments =
|
|
MathJax.InputJax.TeX.Definitions.environment;
|
|
});
|
|
|
|
</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>
|