sample-dynamic-3b.html 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. },
  17. showProcessingMessages: false,
  18. tex2jax: { inlineMath: [['$','$'],['\\(','\\)']] }
  19. });
  20. </script>
  21. <script type="text/javascript" src="../MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
  22. <script>
  23. var Preview = {
  24. typeset: null, // the typeset preview area (filled in by Init below)
  25. preview: null, // the untypeset preview (filled in by Init below)
  26. buffer: null, // the new preview to be typeset (filled in by Init below)
  27. numbers: [], // the equation numbers per paragraph
  28. labels: [], // the equation labels per paragraph
  29. defs: [], // the definitions per paragraph
  30. oldtext: '', // used to see if an update is needed
  31. pending: false, // true when a restart is in the MathJax queue
  32. colorDelay: 400, // how long to leave changed paragraphs colored
  33. timeout: null, // timeout for changed style remover
  34. labelDelay: 1250, // how long to wait before reprocessing for label changes
  35. ltimeout: null, // timeout for changed labels
  36. //
  37. // Get the preview and buffer DIV's
  38. //
  39. Init: function () {
  40. this.typeset = document.getElementById("MathPreview");
  41. this.buffer = document.createElement("div");
  42. this.preview = document.createElement("div");
  43. },
  44. //
  45. // This gets called when a key is pressed in the textarea.
  46. //
  47. Update: function () {
  48. var text = document.getElementById("MathInput").value;
  49. text = text.replace(/^\s+/,'').replace(/\s+$/,'');
  50. if (text !== this.oldtext) {
  51. this.oldtext = text;
  52. if (!this.pending) {
  53. this.pending = true;
  54. MathJax.Hub.Queue(["Restart",this]);
  55. }
  56. }
  57. },
  58. Restart: function (clear,n) {
  59. this.pending = false; this.incremental = true;
  60. var text = this.oldtext.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  61. var text = "<p>"+text.replace(/\n\n+/g,"</p><p>")+"</p>";
  62. // var text = text.replace(/\n\n+/g,"<p>");
  63. this.buffer.innerHTML = text;
  64. if (this.timeout) {clearTimeout(this.timeout); this.timeout = null}
  65. if (this.ltimeout) {clearTimeout(this.ltimeout); this.ltimeout = null}
  66. if (clear) {
  67. this.preview.innerHTML = this.typeset.innerHTML = "";
  68. this.numbers = []; this.labels = []; this.defs = [];
  69. MathJax.Hub.Queue(["resetEquationNumbers",MathJax.InputJax.TeX]);
  70. }
  71. if (n != null) {
  72. this.labels.splice(n); this.numbers.splice(n);
  73. while (this.preview.childNodes.length > n) {
  74. this.preview.removeChild(this.preview.childNodes(n));
  75. this.typeset.removeChild(this.typeset.childNodes(n));
  76. }
  77. }
  78. var update = this.CompareBuffers();
  79. if (update.length) {
  80. MathJax.Hub.Queue(
  81. ["Typeset",MathJax.Hub,update],
  82. ["PostTypeset",this,update,clear]
  83. );
  84. }
  85. },
  86. CompareBuffers: function () {
  87. var i, t1, t2, update = [], n = 0;
  88. var b1 = this.buffer.childNodes,
  89. b2 = this.preview.childNodes,
  90. b3 = this.typeset.childNodes;
  91. var m1 = b1.length, m2 = b2.length, m = Math.min(m1,m2);
  92. //
  93. // Make sure all top-level elements are containers
  94. //
  95. for (i = 0; i < m1; i++) {
  96. t1 = b1[i];
  97. if (typeof(t1.innerHTML) === "undefined") {
  98. this.buffer.replaceChild(document.createElement("span"),t1);
  99. b1[i].appendChild(t1);
  100. }
  101. }
  102. //
  103. // Find first non-matching element,
  104. // and determine the starting equation number
  105. //
  106. for (i = 0; i < m; i++) {
  107. this.removeChanged(b3[i]);
  108. t1 = b1[i].innerHTML, t2 = b2[i].innerHTML;
  109. if (t1 !== t2) break;
  110. n += this.numbers[i];
  111. }
  112. //
  113. // Rest the equation numbers to the starting value
  114. //
  115. MathJax.InputJax.TeX.resetEquationNumbers(n,true);
  116. this.i = i; this.changed = null;
  117. this.refs = []; this.defs = [];
  118. //
  119. // Find last non-matching element
  120. //
  121. while (m1 > i && m2 > i) {
  122. t1 = b1[--m1].innerHTML, t2 = b2[--m2].innerHTML;
  123. this.removeChanged(b3[m2]);
  124. if (t1 !== t2) break;
  125. }
  126. //
  127. // Remove differing elements from typeset copy
  128. // and add in the new (untypeset) elements.
  129. //
  130. var tail = b3[m2+1];
  131. this.recordNumbers(this.numbers.splice(i,m2+1-i),n);
  132. this.recordLabels(this.labels.splice(i,m2+1-i));
  133. while (m2 >= i && b3[i]) {this.typeset.removeChild(b3[i]); m2--}
  134. while (i <= m1 && b1[i]) {
  135. this.numbers.splice(i,0,0); this.labels.splice(i,0,[]);
  136. var node = b1[i].cloneNode(true); update.push(node);
  137. this.typeset.insertBefore(node,tail); i++;
  138. if (node.className && node.className != "")
  139. {node.className += " changed"} else {node.className = "changed"}
  140. }
  141. //
  142. // Swap buffers and set up the new buffer for the next change
  143. //
  144. this.preview = this.buffer; this.buffer = document.createElement("div");
  145. return update;
  146. },
  147. recordNumbers: function (numbers,top) {
  148. this.oldtop = this.newtop = top;
  149. for (var i = 0, m = numbers.length; i < m; i++) {this.oldtop += numbers[i]}
  150. },
  151. recordLabels: function (labels) {
  152. var AMS = MathJax.Extension["TeX/AMSmath"];
  153. this.oldlabels = labels.join(','); this.newlabels = [];
  154. if (labels && labels.length) {
  155. for (var i = 0, m = labels.length; i < m; i++) {
  156. for (var j = 0, n = labels[i].length; j < n; j++) {
  157. delete AMS.labels[labels[i][j].split(/=/)[0]];
  158. }
  159. }
  160. }
  161. },
  162. //
  163. // Remove the "changed" class from an element (leaving all other classes)
  164. //
  165. removeChanged: function (node) {
  166. if (node.className) {
  167. node.className = node.className.toString()
  168. .replace(/(^|\s+)changed(\s|$)/,"$2")
  169. .replace(/^\s+/,"");
  170. }
  171. },
  172. PostTypeset: function (nodes,clear) {
  173. var incremental = this.incremental; this.incremental = false;
  174. if (incremental && this.refs.length) {
  175. var refs = this.refs; this.refs = [];
  176. var queue = MathJax.Callback.Queue(["Reprocess",MathJax.Hub,refs,{}]);
  177. return queue.Push(["PostTypeset",this,nodes,clear]);
  178. }
  179. this.changed = nodes; this.timeout = setTimeout(this.Unmark,this.colorDelay);
  180. if (clear) return;
  181. if (nodes.length !== this.preview.childNodes.length) {
  182. // ### Make delay be dynamic based on number of equations? ###
  183. if (this.newlabels && this.newlabels.join(',') !== this.oldlabels) {
  184. this.ltimeout = setTimeout(this.Relabel,this.labelDelay);
  185. } else if (this.newtop != this.oldtop) {
  186. this.ltimeout = setTimeout(this.Renumber,this.labelDelay);
  187. }
  188. }
  189. },
  190. Unmark: function () {
  191. var nodes = Preview.changed; Preview.changed = Preview.timeout = null;
  192. for (var i = 0, m = nodes.length; i < m; i++) {Preview.removeChanged(nodes[i])}
  193. },
  194. Relabel: function () {
  195. this.pending = true;
  196. MathJax.Hub.Queue(["Restart",Preview,true]);
  197. },
  198. Renumber: function () {
  199. if (Preview.i < Preview.preview.childNodes.length) {
  200. this.pending = true;
  201. MathJax.Hub.Queue(["Restart",Preview,false,Preview.i]);
  202. }
  203. }
  204. };
  205. MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
  206. MathJax.InputJax.TeX.postfilterHooks.Add(function (data) {
  207. if (Preview.incremental) {
  208. var AMS = MathJax.Extension["TeX/AMSmath"];
  209. var labels = Preview.labels[Preview.i];
  210. for (var id in AMS.eqlabels) {if (AMS.eqlabels.hasOwnProperty(id)) {
  211. labels.push(id+"="+AMS.eqlabels[id])
  212. }}
  213. Preview.newlabels = Preview.newlabels.concat(labels);
  214. }
  215. });
  216. });
  217. MathJax.Hub.Register.MessageHook("End Math",function (message) {
  218. if (Preview.incremental) {
  219. var AMS = MathJax.Extension["TeX/AMSmath"];
  220. Preview.numbers[Preview.i++] = AMS.startNumber - Preview.newtop;
  221. Preview.newtop = AMS.startNumber;
  222. }
  223. });
  224. MathJax.Hub.Register.MessageHook("End Math Input",function () {
  225. if (Preview.incremental) {
  226. var AMS = MathJax.Extension["TeX/AMSmath"];
  227. Preview.refs = Preview.refs.concat(AMS.refs);
  228. AMS.refs = [];
  229. }
  230. },5); // priority = 5 to make sure it is before AMS runs.
  231. MathJax.Hub.Register.StartupHook("TeX begingroup Ready",function () {
  232. var STACK = MathJax.InputJax.TeX.eqnStack;
  233. var DEF = STACK.Def;
  234. STACK.Def = function () {
  235. if (Preview.incremental) {Preview.defs.push([].slice.call(arguments,0))}
  236. DEF.apply(this,arguments);
  237. }
  238. });
  239. </script>
  240. </head>
  241. <body>
  242. Type text with embedded TeX in the box below:<br/>
  243. <textarea id="MathInput" cols="60" rows="10" onkeyup="Preview.Update()" onkeydown="Preview.Update()" style="margin-top:5px">
  244. </textarea>
  245. <br/><br/>
  246. Preview is shown here:
  247. <div id="MathPreview" style="border:1px solid; padding: 3px; width:50%; margin-top:5px"></div>
  248. <script>
  249. Preview.Init();
  250. </script>
  251. </body>
  252. </html>