sample-dynamic-3c.html 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Dynamic Preview of Textarea with MathJax Content</title>
  5. <!-- Copyright (c) 2012-2018 The MathJax Consortium -->
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  7. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  8. <style>
  9. .changed { color: red }
  10. </style>
  11. <script type="text/x-mathjax-config">
  12. MathJax.Hub.Config({
  13. TeX: {
  14. equationNumbers: {autoNumber: "AMS"},
  15. extensions: ["begingroup.js"],
  16. noErrors: {disabled: true}
  17. },
  18. showProcessingMessages: false,
  19. tex2jax: { inlineMath: [['$','$'],['\\(','\\)']] }
  20. });
  21. </script>
  22. <script type="text/javascript" src="../MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
  23. <script>
  24. var Preview = {
  25. typeset: null, // the typeset preview area (filled in by Init below)
  26. preview: null, // the untypeset preview (filled in by Init below)
  27. buffer: null, // the new preview to be typeset (filled in by Init below)
  28. numbers: [], // the equation numbers per paragraph
  29. labels: [], // the equation labels per paragraph
  30. defs: [], // the definitions per paragraph
  31. oldtext: '', // used to see if an update is needed
  32. pending: false, // true when a restart is in the MathJax queue
  33. colorDelay: 400, // how long to leave changed paragraphs colored
  34. ctimeout: null, // timeout for changed style remover
  35. labelDelay: 1250, // how long to wait before reprocessing for label changes
  36. ltimeout: null, // timeout for changed labels
  37. //
  38. // Get the preview and buffer DIV's
  39. //
  40. Init: function () {
  41. this.typeset = document.getElementById("MathPreview");
  42. this.buffer = document.createElement("div");
  43. this.preview = document.createElement("div");
  44. },
  45. //
  46. // This gets called when a key is pressed in the textarea.
  47. //
  48. Update: function () {
  49. var text = document.getElementById("MathInput").value;
  50. text = text.replace(/^\s+/,'').replace(/\s+$/,'');
  51. if (text !== this.oldtext) {
  52. this.oldtext = text;
  53. if (!this.pending) {
  54. this.pending = true;
  55. MathJax.Hub.Queue(["Restart",this]);
  56. }
  57. }
  58. },
  59. Restart: function (from) {
  60. this.pending = false;
  61. var text = this.oldtext.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  62. var text = "<p>"+text.replace(/\n\n+/g,"</p><p>")+"</p>";
  63. // var text = text.replace(/\n\n+/g,"<p>");
  64. this.buffer.innerHTML = text;
  65. if (this.ctimeout) {clearTimeout(this.ctimeout); this.ctimeout = null}
  66. if (this.ltimeout) {clearTimeout(this.ltimeout); this.ltimeout = null}
  67. var update = this.CompareBuffers(from);
  68. if (update.needed) {
  69. MathJax.Hub.Queue(
  70. ["PreTypeset",this,update],
  71. ["Typeset",this,update],
  72. ["PostTypeset",this,update]
  73. );
  74. }
  75. },
  76. CompareBuffers: function (from) {
  77. var b1 = this.buffer.childNodes,
  78. b2 = this.preview.childNodes,
  79. i, m1 = b1.length, m2 = b2.length;
  80. //
  81. // Make sure all top-level elements are containers
  82. //
  83. for (i = 0; i < m1; i++) {
  84. var node = b1[i];
  85. if (typeof(node.innerHTML) === "undefined") {
  86. this.buffer.replaceChild(document.createElement("span"),node);
  87. b1[i].appendChild(node);
  88. }
  89. }
  90. //
  91. // Determine the range of elements to update
  92. //
  93. if (from != null) {
  94. //
  95. // If from a starting point to the end, return the proper range
  96. //
  97. i = from; m1--; m2--;
  98. } else {
  99. //
  100. // Find first non-matching element, if any,
  101. // and the last non-matching element
  102. //
  103. m = Math.min(m1,m2);
  104. for (i = 0; i < m; i++) {if (b1[i].innerHTML !== b2[i].innerHTML) break}
  105. if (i === m && m1 === m2) {return {needed: false}}
  106. while (m1 > i && m2 > i) {if (b1[--m1].innerHTML !== b2[--m2].innerHTML) break}
  107. }
  108. return {needed:true, start:i, end1:m1, end2:m2};
  109. },
  110. Typeset: function (update) {
  111. return MathJax.Hub.Typeset(update.nodes);
  112. },
  113. PreTypeset: function (update) {
  114. var TEX = MathJax.InputJax.TeX;
  115. var i, m, n = 0, defs = [], m1 = update.end1, m2 = update.end2;
  116. var b1 = this.buffer.childNodes,
  117. b2 = this.typeset.childNodes;
  118. //
  119. // Remove the change color, if any
  120. //
  121. if (this.changed) {this.Unmark()}
  122. //
  123. // Determine the starting equation number
  124. //
  125. for (i = 0, m = update.start; i < m; i++) {
  126. n += this.numbers[i];
  127. defs = defs.concat(this.defs[i]);
  128. }
  129. TEX.resetEquationNumbers(n,true);
  130. //
  131. // Pop any left over \begingroups and push a new one
  132. // Then define any macros from previous paragraphs
  133. //
  134. while (TEX.rootStack.top > 1) {TEX.rootStack.stack.pop(); TEX.rootStack.top--}
  135. TEX.rootStack.Push(TEX.nsStack.nsFrame());
  136. for (i = 0, m = defs.length; i < m; i++) {TEX.rootStack.Def.apply(TEX.rootStack,defs[i])}
  137. i = this.i = update.start; this.refs = []; this.defs.all = [];
  138. //
  139. // Remove differing elements from typeset copy
  140. // and add in the new (untypeset) elements.
  141. //
  142. m = m2+1; update.nodes = [];
  143. var tail = b2[m];
  144. this.recordNumbers(this.numbers.splice(i,m-i),n);
  145. this.recordLabels(this.labels.splice(i,m-i));
  146. this.recordDefs(this.defs.splice(i,m-i));
  147. while (m2 >= i && b2[i]) {this.typeset.removeChild(b2[i]); m2--}
  148. while (i <= m1 && b1[i]) {
  149. this.numbers.splice(i,0,0); this.labels.splice(i,0,[]); this.defs.splice(i,0,[]);
  150. var node = b1[i].cloneNode(true); update.nodes.push(node);
  151. this.typeset.insertBefore(node,tail); i++;
  152. if (node.className && node.className != "")
  153. {node.className += " changed"} else {node.className = "changed"}
  154. }
  155. //
  156. // Swap buffers and set up the new buffer for the next change
  157. //
  158. this.preview = this.buffer; this.buffer = document.createElement("div");
  159. this.incremental = true;
  160. },
  161. recordNumbers: function (numbers,top) {
  162. this.oldtop = this.newtop = top;
  163. for (var i = 0, m = numbers.length; i < m; i++) {this.oldtop += numbers[i]}
  164. },
  165. recordLabels: function (labels) {
  166. var AMS = MathJax.Extension["TeX/AMSmath"];
  167. this.oldlabels = labels.join(''); this.newlabels = [];
  168. if (labels && labels.length) {
  169. for (var i = 0, m = labels.length; i < m; i++) {
  170. for (var j = 0, n = labels[i].length; j < n; j++) {
  171. delete AMS.labels[labels[i][j].split(/=/)[0]];
  172. }
  173. }
  174. }
  175. },
  176. recordDefs: function (defs) {
  177. var all = [];
  178. for (var i = 0, m = defs.length; i < m; i++) {all.push(defs[i].all)}
  179. this.defs.old = all.join(",");
  180. },
  181. PostTypeset: function (update) {
  182. var incremental = this.incremental; this.incremental = false;
  183. if (incremental && this.refs.length) {
  184. var refs = this.refs; this.refs = [];
  185. var queue = MathJax.Callback.Queue(["Reprocess",MathJax.Hub,refs,{}]);
  186. return queue.Push(["PostTypeset",this,update]);
  187. }
  188. this.changed = update.nodes; this.ctimeout = setTimeout(this.Unmark,this.colorDelay);
  189. if (update.nodes.length !== this.preview.childNodes.length) {
  190. // ### Make delay be dynamic based on number of equations? ###
  191. if (this.needsRefresh || this.newlabels && this.newlabels.join('') !== this.oldlabels) {
  192. this.needsRefresh = true;
  193. this.ltimeout = setTimeout(this.Refresh,this.labelDelay);
  194. } else {
  195. if (this.newtop != this.oldtop || this.defs.all.join("") !== this.defs.old) {
  196. if (this.needsRenumber == null) {this.needsRenumber = this.i}
  197. else {this.needsRenumber = Math.min(this.needsRenumber,this.i)}
  198. }
  199. if (this.needsRenumber != null)
  200. {this.ltimeout = setTimeout(this.Renumber,this.labelDelay)}
  201. }
  202. }
  203. },
  204. Unmark: function () {
  205. var nodes = Preview.changed; Preview.changed = Preview.ctimeout = null;
  206. for (var i = 0, m = nodes.length; i < m; i++) {Preview.removeChanged(nodes[i])}
  207. },
  208. Refresh: function () {
  209. Preview.pending = true; Preview.needsRefresh = false; delete Preview.needsRenumber;
  210. MathJax.Hub.Queue(["Restart",Preview,0]);
  211. },
  212. Renumber: function () {
  213. if (Preview.needsRenumber < Preview.preview.childNodes.length) {
  214. var n = Preview.needsRenumber;
  215. Preview.pending = true; delete Preview.needsRenumber;
  216. MathJax.Hub.Queue(["Restart",Preview,n]);
  217. }
  218. },
  219. //
  220. // Remove the "changed" class from an element (leaving all other classes)
  221. //
  222. removeChanged: function (node) {
  223. if (node.className) {
  224. node.className = node.className.toString()
  225. .replace(/(^|\s+)changed(\s|$)/,"$2")
  226. .replace(/^\s+/,"");
  227. }
  228. }
  229. };
  230. MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
  231. MathJax.InputJax.TeX.postfilterHooks.Add(function (data) {
  232. if (Preview.incremental) {
  233. var AMS = MathJax.Extension["TeX/AMSmath"];
  234. var labels = Preview.labels[Preview.i];
  235. for (var id in AMS.eqlabels) {if (AMS.eqlabels.hasOwnProperty(id)) {
  236. labels.push(id+"="+AMS.eqlabels[id])
  237. }}
  238. Preview.newlabels = Preview.newlabels.concat(labels);
  239. }
  240. });
  241. });
  242. MathJax.Hub.Register.MessageHook("Begin Math Input",function () {
  243. if (Preview.incremental) {Preview.eqDefs = []; Preview.eqDefs.all = []}
  244. });
  245. MathJax.Hub.Register.MessageHook("End Math Input",function () {
  246. if (Preview.incremental) {
  247. var AMS = MathJax.Extension["TeX/AMSmath"];
  248. Preview.refs = Preview.refs.concat(AMS.refs); AMS.refs = [];
  249. Preview.eqDefs.all = Preview.eqDefs.all.join("");
  250. Preview.defs[Preview.i] = Preview.eqDefs;
  251. Preview.defs.all.push(Preview.defs[Preview.i].all);
  252. Preview.numbers[Preview.i] = AMS.startNumber - Preview.newtop;
  253. Preview.newtop = AMS.startNumber;
  254. Preview.i++;
  255. }
  256. },5); // priority = 5 to make sure it is before AMS runs.
  257. MathJax.Hub.Register.StartupHook("TeX begingroup Ready",function () {
  258. var STACK = MathJax.InputJax.TeX.eqnStack;
  259. var DEF = STACK.Def;
  260. STACK.Def = function () {
  261. if (Preview.incremental) {
  262. Preview.eqDefs.push([].slice.call(arguments,0));
  263. Preview.eqDefs.all.push(arguments[0]+"{"+arguments[1]+"}");
  264. }
  265. DEF.apply(this,arguments);
  266. }
  267. //
  268. // Temporary hack to fix typo in begingroup.js
  269. //
  270. MathJax.InputJax.TeX.rootStack.stack[0].environments =
  271. MathJax.InputJax.TeX.Definitions.environment;
  272. });
  273. </script>
  274. </head>
  275. <body>
  276. Type text with embedded TeX in the box below:<br/>
  277. <textarea id="MathInput" cols="60" rows="10" onkeyup="Preview.Update()" onkeydown="Preview.Update()" style="margin-top:5px">
  278. </textarea>
  279. <br/><br/>
  280. Preview is shown here:
  281. <div id="MathPreview" style="border:1px solid; padding: 3px; width:50%; margin-top:5px"></div>
  282. <script>
  283. Preview.Init();
  284. </script>
  285. </body>
  286. </html>