sample-dynamic-3d.html 10 KB

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