Annotation of loncom/html/adm/LC_math_editor/src/ui.js, revision 1.2
1.1 damieng 1: /*
2:
3: Copyright (C) 2014 Michigan State University Board of Trustees
4:
5: The JavaScript code in this page is free software: you can
6: redistribute it and/or modify it under the terms of the GNU
7: General Public License (GNU GPL) as published by the Free Software
8: Foundation, either version 3 of the License, or (at your option)
9: any later version. The code is distributed WITHOUT ANY WARRANTY;
10: without even the implied warranty of MERCHANTABILITY or FITNESS
11: FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
12:
13: As additional permission under GNU GPL version 3 section 7, you
14: may distribute non-source (e.g., minimized or compacted) forms of
15: that code without the copy of the GNU GPL normally required by
16: section 4, provided you include this license notice and a URL
17: through which recipients can access the Corresponding Source.
18:
19: */
20:
21: "use strict";
22:
23: var handleChange = function(math_object) {
24: // math_object has 3 fields: ta, output_node, oldtxt
25: // we need to pass this object instead of the values because oldtxt will change
26: var ta, output_node, txt, parser, output, root, test1, test2;
27: ta = math_object.ta;
28: output_node = math_object.output_node;
29: txt = ta.value;
30:
31: // automatically add brackets to something like "1;2;3", for LON-CAPA:
32: // NOTE: this is ugly and sometimes adds brackets to error messages
33: test1 = '';
34: test2 = txt;
35: while (test2 != test1) {
36: test1 = test2;
37: test2 = test1.replace(/\[[^\[\]]*\]/g, '');
38: }
39: if (test2.split("[").length == test2.split("]").length) {
40: test1 = '';
41: while (test2 != test1) {
42: test1 = test2;
43: test2 = test1.replace(/\([^\(\)]*\)/g, '');
44: }
45: if (test2.split("(").length == test2.split(")").length) {
1.2 ! damieng 46: test1 = '';
! 47: while (test2 != test1) {
! 48: test1 = test2;
! 49: test2 = test1.replace(/\{[^\{\}]*\}/g, '');
! 50: }
! 51: if (test2.split("{").length == test2.split("}").length) {
! 52: if (test2.indexOf(Definitions.ARG_SEPARATOR) != -1) {
! 53: txt = '['+txt+']';
! 54: }
1.1 damieng 55: }
56: }
57: }
58:
59: if (txt != math_object.oldtxt) {
60: math_object.oldtxt = txt;
61: while (output_node.firstChild != null)
62: output_node.removeChild(output_node.firstChild);
63: output_node.removeAttribute("title");
64: if (txt != "") {
65: parser = math_object.parser;
66: try {
67: root = parser.parse(txt);
68: if (root != null) {
69: var math = document.createElement("math");
70: math.setAttribute("display", "block");
71: math.appendChild(root.toMathML());
72: output_node.appendChild(math);
73: MathJax.Hub.Queue(["Typeset", MathJax.Hub, output_node]);
74: }
75: } catch (e) {
76: output = "error: " + e;
77: output_node.setAttribute("title", output);
78: if (e instanceof ParseException) {
79: output_node.appendChild(document.createTextNode(txt.substring(0, e.from)));
80: var span = document.createElement('span');
81: span.appendChild(document.createTextNode(txt.substring(e.from, e.to + 1)));
82: span.className = 'math-error';
83: output_node.appendChild(span);
84: if (e.to < txt.length - 1) {
85: output_node.appendChild(document.createTextNode(txt.substring(e.to + 1)));
86: }
87: } else {
88: var tn = document.createTextNode(output);
89: output_node.appendChild(tn);
90: }
91: }
92: }
93: }
94: }
95:
1.2 ! damieng 96: var math_objects = [];
1.1 damieng 97:
98: /*
99: Looks for elements with the "math" class, and
100: adds a preview div afterward which is updated automatically.
1.2 ! damieng 101: Can be called again after math fields have been added, removed, or when options have changed.
1.1 damieng 102: */
103: var initEditors = function() {
1.2 ! damieng 104: // to hide the MathJax messages (note: this could be done elsewhere)
! 105: MathJax.Hub.Config({
! 106: messageStyle: "none"
! 107: });
1.1 damieng 108: var math_inputs = document.getElementsByClassName('math');
1.2 ! damieng 109: // first remove the nodes and objects for the inputs that are gone
! 110: for (var i=0; i<math_objects.length; i++) {
! 111: var ta = math_objects[i].ta;
! 112: var found = false;
! 113: for (var j=0; j<math_inputs.length; j++) {
! 114: if (math_inputs[j] == ta) {
! 115: found = true;
! 116: break;
! 117: }
! 118: }
! 119: if (!found) {
! 120: var output_node = math_objects[i].output_node;
! 121: if (output_node.parentNode) {
! 122: output_node.parentNode.removeChild(output_node);
! 123: }
! 124: math_objects.splice(i, 1);
! 125: i--;
! 126: }
! 127: }
! 128: // then create or update nodes and objects for the new inputs
1.1 damieng 129: for (var i=0; i<math_inputs.length; i++) {
130: var ta = math_inputs[i];
131: if (ta.nodeName == "TEXTAREA" || ta.nodeName == "INPUT") {
1.2 ! damieng 132: var ind_math = -1;
! 133: for (var j=0; j<math_objects.length; j++) {
! 134: if (math_objects[j].ta == ta) {
! 135: ind_math = j;
! 136: break;
! 137: }
! 138: }
1.1 damieng 139: var implicit_operators = (ta.getAttribute("data-implicit_operators") === "true");
140: var unit_mode = (ta.getAttribute("data-unit_mode") === "true");
141: var constants = ta.getAttribute("data-constants");
142: if (constants)
143: constants = constants.split(/[\s,]+/);
1.2 ! damieng 144: var output_node;
! 145: if (ind_math == -1) {
! 146: output_node = document.createElement("span");
! 147: output_node.style.display = "none";
! 148: output_node.style.position = "absolute";
! 149: output_node.style.backgroundColor = "rgba(255,255,224,0.9)";
! 150: output_node.style.color = "black";
! 151: output_node.style.border = "1px solid #A0A0A0";
! 152: output_node.style.padding = "5px";
! 153: output_node.style.zIndex = "1";
! 154: var getCSSAbsolutePosition = function getCSSAbsolutePosition(el) {
! 155: var x = 0;
! 156: var y = 0;
! 157: while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
! 158: x += el.offsetLeft - el.scrollLeft;
! 159: y += el.offsetTop - el.scrollTop;
! 160: el = el.offsetParent;
! 161: if (el) {
! 162: var style = window.getComputedStyle(el);
! 163: if (style.position == 'absolute' || style.position == 'relative')
! 164: break;
! 165: }
! 166: }
! 167: return {top: y, left: x};
! 168: }
! 169: var place = function(ta, output_node) {
! 170: // position the output_node below or on top of ta
! 171: var ta_rect = ta.getBoundingClientRect();
! 172: var root = document.documentElement;
! 173: var docTop = (window.pageYOffset || root.scrollTop) - (root.clientTop || 0);
! 174: var docLeft = (window.pageXOffset || root.scrollLeft) - (root.clientLeft || 0);
! 175: var ta_pos = getCSSAbsolutePosition(ta);
! 176: output_node.style.left = ta_pos.left + "px";
! 177: if (window.innerHeight > ta_rect.bottom + output_node.offsetHeight)
! 178: output_node.style.top = (ta_pos.top + ta.offsetHeight) + "px";
! 179: else
! 180: output_node.style.top = (ta_pos.top - output_node.offsetHeight) + "px";
! 181: }
! 182: if (ta.nextSibling)
! 183: ta.parentNode.insertBefore(output_node, ta.nextSibling);
! 184: else
! 185: ta.parentNode.appendChild(output_node);
! 186: var hide_node = function(an_output_node) {
! 187: // returns a function that will hide the node on any event
! 188: // (we can't use the node directly because it changes in the loop)
! 189: return function(e) {
! 190: an_output_node.style.display = "none";
! 191: };
! 192: }
! 193: var focus = function(a_ta, an_output_node) {
! 194: return function(e) {
! 195: if (a_ta.value != '') {
! 196: an_output_node.style.display = "block";
! 197: place(a_ta, an_output_node);
! 198: }
! 199: };
1.1 damieng 200: };
1.2 ! damieng 201: ta.addEventListener("blur", hide_node(output_node), false);
! 202: ta.addEventListener("focus", focus(ta, output_node), false);
! 203: output_node.addEventListener("mouseenter", hide_node(output_node), false);
! 204: ind_math = math_objects.length;
! 205: var oldtxt = "";
! 206: math_objects[ind_math] = {
! 207: "ta": ta,
! 208: "output_node": output_node,
! 209: "oldtxt": oldtxt,
! 210: "parser": new Parser(implicit_operators, unit_mode, constants)
! 211: };
! 212: var changeObjectN = function(n) {
! 213: return function(e) {
! 214: var obj = math_objects[n];
! 215: handleChange(obj);
! 216: if (document.activeElement == obj.ta) {
! 217: if (obj.ta.value != '') {
! 218: obj.output_node.style.display = "block";
! 219: MathJax.Hub.Queue(function () {
! 220: // position the element only when MathJax is done, because the output_node height might change
! 221: place(obj.ta, obj.output_node);
! 222: });
! 223: } else {
! 224: obj.output_node.style.display = "none";
! 225: }
! 226: }
! 227: };
! 228: };
! 229: var startChange = changeObjectN(ind_math);
! 230: if (ta.value != oldtxt)
! 231: startChange(); // process non-empty fields even though they are not visible yet
! 232: ta.addEventListener('change', startChange, false);
! 233: ta.addEventListener('keyup', startChange, false);
! 234: } else {
! 235: // only create a new parser and update the result if the options have changed
! 236: var same_constants;
! 237: var parser = math_objects[ind_math].parser;
! 238: if (!constants && parser.constants.length == 0) {
! 239: same_constants = true;
! 240: } else {
! 241: if (constants) {
! 242: same_constants = parser.constants.length == constants.length;
! 243: if (same_constants) {
! 244: for (var j=0; j<constants.length; j++) {
! 245: if (parser.constants[j] != constants[j]) {
! 246: same_constants = false;
! 247: break;
! 248: }
! 249: }
! 250: }
! 251: } else {
! 252: same_constants = false;
! 253: }
! 254: }
! 255: if (parser.implicit_operators != implicit_operators || parser.unit_mode != unit_mode || !same_constants) {
! 256: math_objects[ind_math].parser = new Parser(implicit_operators, unit_mode, constants);
! 257: if (ta.value != '') {
! 258: math_objects[ind_math].oldtxt = '';
! 259: handleChange(math_objects[ind_math]);
! 260: }
! 261: }
! 262: }
1.1 damieng 263: }
264: }
265: }
266:
1.2 ! damieng 267: /**
! 268: * Updates display for <span class="math"> and <div class="math"> (LON-CAPA ln and dlm tags)
! 269: */
! 270: var updateMathSpanAndDiv = function() {
! 271: var nl = document.getElementsByClassName('math');
! 272: // convert to an array because the nodelist would change as we are removing nodes from the document
! 273: var math_nodes = [];
! 274: for (var i = 0, ref = math_nodes.length = nl.length; i < ref; i++) {
! 275: math_nodes[i] = nl[i];
! 276: }
! 277: for (var i=0; i<math_nodes.length; i++) {
! 278: var el = math_nodes[i];
! 279: if (el.nodeName == "SPAN" || el.nodeName == "DIV") {
! 280: if (el.firstChild == null || el.firstChild.nodeType != 3)
! 281: continue;
! 282: var bspan = (el.nodeName == "SPAN");
! 283: var txt = el.firstChild.nodeValue;
! 284: var implicit_operators = (el.getAttribute("data-implicit_operators") === "true");
! 285: var unit_mode = (el.getAttribute("data-unit_mode") === "true");
! 286: var constants = el.getAttribute("data-constants");
! 287: if (constants)
! 288: constants = constants.split(/[\s,]+/);
! 289: var parser = new Parser(implicit_operators, unit_mode, constants);
! 290: try {
! 291: var root = parser.parse(txt);
! 292: if (root != null) {
! 293: var math = document.createElement("math");
! 294: math.setAttribute("display", bspan ? "inline" : "block");
! 295: math.appendChild(root.toMathML(['#000000']));
! 296: // at this point it would be nice to replace el by math, but MathJax does not
! 297: // always typeset math elements when given directly, so we need to typeset the parent...
! 298: el.classList.remove('math');
! 299: el.removeChild(el.firstChild);
! 300: el.appendChild(math);
! 301: MathJax.Hub.Queue(["Typeset", MathJax.Hub, el]);
! 302: }
! 303: } catch (e) {
! 304: el.firstChild.nodeValue = "[syntax error in math:" + e + "]";
! 305: el.classList.remove('math');
! 306: }
! 307: }
! 308: }
! 309: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>