@zzish/math-rich-input 0.1.48 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,10 @@
1
1
  import React from "react";
2
2
  import { addStyles, EditableMathField } from "react-mathquill";
3
3
  import SymbolButton from "./SymbolButton.jsx";
4
- import { getSymbolsForButtonArea, getLatexInsertionParams } from "./equationEditorButtonsHelper.js";
4
+ import {
5
+ getSymbolsForButtonArea,
6
+ getLatexInsertionParams,
7
+ } from "./equationEditorButtonsHelper.js";
5
8
 
6
9
  import "./EquationEditor.css";
7
10
 
@@ -24,38 +27,38 @@ let GLOBAL_lastSymbols = ["", "", ""];
24
27
  const MAX_BUTTON_ROWS = 5;
25
28
  const TAB_NAMES = ["Common", "More", "Greek", "Chemistry", "Physics"];
26
29
  const BUTTON_AREA_NAMES = [
27
- "Common",
28
- "Operators",
29
- "Angles",
30
- "Misc",
31
- "Sets",
32
- "GreekLower",
33
- "GreekUpper",
34
- "Chemistry",
35
- "Elements",
36
- "Physics1",
37
- "Physics2",
38
- "Physics3",
30
+ "Common",
31
+ "Operators",
32
+ "Angles",
33
+ "Misc",
34
+ "Sets",
35
+ "GreekLower",
36
+ "GreekUpper",
37
+ "Chemistry",
38
+ "Elements",
39
+ "Physics1",
40
+ "Physics2",
41
+ "Physics3",
39
42
  ];
40
43
  const TAB_NAMES_EXPERT = ["Common", "More", "Greek", "Expert"];
41
44
  const BUTTON_AREA_NAMES_EXPERT = [
42
- "Common",
43
- "Operators",
44
- "Angles",
45
- "Misc",
46
- "Sets",
47
- "GreekLower",
48
- "GreekUpper",
49
- "Expert",
50
- "Matrices",
45
+ "Common",
46
+ "Operators",
47
+ "Angles",
48
+ "Misc",
49
+ "Sets",
50
+ "GreekLower",
51
+ "GreekUpper",
52
+ "Expert",
53
+ "Matrices",
51
54
  ];
52
55
  const TAB_STARTS_AT_AREA_NAMED = {
53
- Common: "Common",
54
- More: "Misc",
55
- Greek: "GreekLower",
56
- Chemistry: "Chemistry",
57
- Physics: "Physics1",
58
- Expert: "Expert",
56
+ Common: "Common",
57
+ More: "Misc",
58
+ Greek: "GreekLower",
59
+ Chemistry: "Chemistry",
60
+ Physics: "Physics1",
61
+ Expert: "Expert",
59
62
  };
60
63
 
61
64
  let GLOBAL_buttonAreasWidths = null;
@@ -63,536 +66,563 @@ let GLOBAL_buttonAreasWidthsExpert = null;
63
66
  let GLOBAL_last_selected_tab = TAB_NAMES[0];
64
67
 
65
68
  export default class EquationEditor extends React.Component {
66
- constructor(props) {
67
- super(props);
68
- this.state = {
69
- latex: props.latex,
70
- pretty: false,
71
- selectedTab: GLOBAL_last_selected_tab,
72
- recentSymbolsCount: 0,
73
- };
74
-
75
- this.mathQuill = null;
76
- this.handleInsertCallback = props.handleInsert;
77
- this.handleCancelCallback = props.handleCancel;
78
- }
79
-
80
- handleInsert = () => {
81
- this.handleInsertCallback(this.state.latex);
69
+ constructor(props) {
70
+ super(props);
71
+ this.state = {
72
+ latex: props.latex,
73
+ pretty: false,
74
+ selectedTab: GLOBAL_last_selected_tab,
75
+ recentSymbolsCount: 0,
82
76
  };
83
77
 
84
- handleCancel = () => {
85
- this.handleCancelCallback();
86
- };
87
-
88
- prettifyLatex(latex) {
89
- // NB: replaceAll not suported on safari
90
- // console.log("Before: "+latex)
91
- // latex = latex.replaceAll(/\\\\/g,"_DOOUBLE_BCACKSLASH_")
92
- // latex = latex.replace(/\\/g," \\")
93
- // latex = latex.replaceAll("_DOOUBLE_BCACKSLASH_","\\\\")
94
- // latex = latex.replaceAll("="," = ")
95
- // latex = latex.replaceAll("+"," + ")
96
- // latex = latex.replaceAll("-"," - ")
97
- // latex = latex.replaceAll("{ ","{")
98
- // latex = latex.replaceAll("( ","(")
99
- // latex = latex.replaceAll("[ ","[")
100
- // latex = latex.replaceAll(" "," ")
101
- // latex = latex.trim()
102
- // console.log("Prettified: "+latex)
103
- return latex;
78
+ this.mathQuill = null;
79
+ this.handleInsertCallback = props.handleInsert;
80
+ this.handleCancelCallback = props.handleCancel;
81
+ }
82
+
83
+ handleInsert = () => {
84
+ this.handleInsertCallback(this.state.latex);
85
+ };
86
+
87
+ handleCancel = () => {
88
+ this.handleCancelCallback();
89
+ };
90
+
91
+ prettifyLatex(latex) {
92
+ // NB: replaceAll not suported on safari
93
+ // console.log("Before: "+latex)
94
+ // latex = latex.replaceAll(/\\\\/g,"_DOOUBLE_BCACKSLASH_")
95
+ // latex = latex.replace(/\\/g," \\")
96
+ // latex = latex.replaceAll("_DOOUBLE_BCACKSLASH_","\\\\")
97
+ // latex = latex.replaceAll("="," = ")
98
+ // latex = latex.replaceAll("+"," + ")
99
+ // latex = latex.replaceAll("-"," - ")
100
+ // latex = latex.replaceAll("{ ","{")
101
+ // latex = latex.replaceAll("( ","(")
102
+ // latex = latex.replaceAll("[ ","[")
103
+ // latex = latex.replaceAll(" "," ")
104
+ // latex = latex.trim()
105
+ // console.log("Prettified: "+latex)
106
+ return latex;
107
+ }
108
+
109
+ handleChange = (mathQuill) => {
110
+ this.setState({ latex: mathQuill.latex(), pretty: false });
111
+ };
112
+
113
+ handleKeyDown = (e) => {
114
+ try {
115
+ if (!this.props.useExpertMode && e.key === "Enter") {
116
+ this.handleInsertCallback(this.state.latex);
117
+ e.preventDefault();
118
+ }
119
+ } catch (error) {
120
+ console.error(error);
121
+ }
122
+ };
123
+
124
+ updateRecentSymbols(symbol) {
125
+ let recentSymbols = GLOBAL_recentSymbols;
126
+ let symbolIndex = -1;
127
+ let minIndex = -1;
128
+ let min = 999999;
129
+ for (let i = 0; i < recentSymbols.length; i++) {
130
+ if (recentSymbols[i].name === symbol) symbolIndex = i;
131
+ if (recentSymbols[i].count > 0)
132
+ recentSymbols[i].count = recentSymbols[i].count * RECENT_DECAY_FACTOR;
133
+ if (recentSymbols[i].count <= min) {
134
+ min = recentSymbols[i].count;
135
+ minIndex = i;
136
+ }
137
+ }
138
+ if (symbolIndex >= 0) {
139
+ recentSymbols[symbolIndex].count =
140
+ recentSymbols[symbolIndex].count + RECENT_WEIGHT;
141
+ } else {
142
+ if (recentSymbols.length >= RECENT_MAX) {
143
+ recentSymbols.splice(minIndex, 1);
144
+ }
145
+ recentSymbols.push({ name: symbol, count: RECENT_WEIGHT });
104
146
  }
105
147
 
106
- handleChange = (mathQuill) => {
107
- this.setState({ latex: mathQuill.latex(), pretty: false });
108
- };
109
-
110
- handleKeyDown = (e) => {
111
- // console.log(e.key)
112
- try {
113
- if (!this.props.useExpertMode && e.key === "Enter") {
114
- this.handleInsertCallback(this.state.latex);
115
- e.preventDefault();
116
- }
117
- } catch (error) {
118
- console.error(error);
119
- }
120
- };
121
-
122
- updateRecentSymbols(symbol) {
123
- let recentSymbols = GLOBAL_recentSymbols;
124
- let symbolIndex = -1;
125
- let minIndex = -1;
126
- let min = 999999;
127
- for (let i = 0; i < recentSymbols.length; i++) {
128
- if (recentSymbols[i].name === symbol) symbolIndex = i;
129
- if (recentSymbols[i].count > 0)
130
- recentSymbols[i].count = recentSymbols[i].count * RECENT_DECAY_FACTOR;
131
- if (recentSymbols[i].count <= min) {
132
- min = recentSymbols[i].count;
133
- minIndex = i;
134
- }
135
- }
136
- if (symbolIndex >= 0) {
137
- recentSymbols[symbolIndex].count = recentSymbols[symbolIndex].count + RECENT_WEIGHT;
148
+ // recentSymbols.sort((a, b) => b.count - a.count) // Order max to min
149
+
150
+ GLOBAL_recentSymbols = recentSymbols;
151
+ GLOBAL_lastSymbols.pop();
152
+ GLOBAL_lastSymbols.unshift(symbol);
153
+
154
+ this.setState({ recentSymbolsCount: this.state.recentSymbolsCount + 1 });
155
+ }
156
+
157
+ handleInsertSymbol = (symbol) => {
158
+ if (this.props.useExpertMode) this.handleInsertSymbolKatex(symbol);
159
+ else this.handleInsertSymbolMathQuill(symbol);
160
+ };
161
+
162
+ isLetter = (char) => {
163
+ return ("a" <= char && char <= "z") || ("A" <= char && char <= "Z");
164
+ };
165
+
166
+ // Returns:
167
+ // 1. the provided pos if pos is not inside a latex tag
168
+ // 2. a new pos if pos is close to the start or end of a tag (within 2 characters)
169
+ // 3. -1 if pos is not close to the start or end of a tag
170
+ movePosIfInsideTag(latex, pos) {
171
+ if (pos === 0 || pos === latex.length) return pos;
172
+
173
+ let c = latex.charAt(pos);
174
+ if (!this.isLetter(c)) return pos;
175
+ let cm1 = latex.charAt(pos - 1);
176
+ if (!this.isLetter(cm1) && cm1 !== "\\") return pos - 1;
177
+
178
+ // We are inside a latex tag, let's move out of it
179
+ if (pos === 1) return 0;
180
+ if (pos === latex.length - 1) return latex.length;
181
+
182
+ if (latex.charAt(pos - 1) === "\\") return pos - 1;
183
+ if (!this.isLetter(latex.charAt(pos + 1))) return pos + 1;
184
+ if (latex.charAt(pos - 2) === "\\") return pos - 2;
185
+ if (!this.isLetter(latex.charAt(pos + 2))) return pos + 2;
186
+
187
+ return -1;
188
+ }
189
+
190
+ handleInsertSymbolKatex = (symbol) => {
191
+ let oldLatex = null;
192
+ let start = -1;
193
+ let end = -1;
194
+ try {
195
+ this.updateRecentSymbols(symbol);
196
+ oldLatex = this.latexInput.value;
197
+ start = this.latexInput.selectionStart;
198
+ end = this.latexInput.selectionEnd;
199
+
200
+ start = this.movePosIfInsideTag(oldLatex, start);
201
+ end = this.movePosIfInsideTag(oldLatex, end);
202
+ if (start === -1 || end === -1) {
203
+ console.log(
204
+ "Can't insert as cursor position or start or end of selection is inside a tag"
205
+ );
206
+ return;
207
+ }
208
+
209
+ // if (this.latexInput !== document.activeElement)
210
+ this.latexInput.focus();
211
+
212
+ let insertParams = getLatexInsertionParams(symbol);
213
+ let insert = insertParams.latexWithMarks;
214
+ // console.log("Insert params: ");
215
+ // console.log(insertParams);
216
+ // console.log(start + ":" + end);
217
+ // console.log(oldLatex);
218
+
219
+ // Move text before cursor into latex if double mark exists in latex
220
+ let pos = insert.indexOf("##");
221
+ if (pos >= 0) {
222
+ if (start !== end) {
223
+ insert = insert.replace("##", oldLatex.substring(start, end));
224
+ // console.log("insert: " + insert);
225
+ oldLatex = oldLatex.substring(0, start) + oldLatex.substring(end);
226
+ end = start;
227
+ } else if (start > 0) {
228
+ insert = insert.replace("##", oldLatex.charAt(start - 1));
229
+ oldLatex =
230
+ oldLatex.substring(0, start - 1) + oldLatex.substring(start);
231
+ start = start - 1;
232
+ end = start;
138
233
  } else {
139
- if (recentSymbols.length >= RECENT_MAX) {
140
- recentSymbols.splice(minIndex, 1);
141
- }
142
- recentSymbols.push({ name: symbol, count: RECENT_WEIGHT });
234
+ insert = insert.replace("##", "#");
143
235
  }
144
-
145
- // recentSymbols.sort((a, b) => b.count - a.count) // Order max to min
146
-
147
- GLOBAL_recentSymbols = recentSymbols;
148
- GLOBAL_lastSymbols.pop();
149
- GLOBAL_lastSymbols.unshift(symbol);
150
-
151
- this.setState({ recentSymbolsCount: this.state.recentSymbolsCount + 1 });
236
+ }
237
+ // Move text before cursor into latex if selection made and single mark exists in latex
238
+ // or otherwise delete selected text
239
+ if (start !== end) {
240
+ pos = insert.indexOf("#");
241
+ if (pos >= 0)
242
+ insert = insert.replace("#", oldLatex.substring(start, end));
243
+
244
+ oldLatex = oldLatex.substring(0, start) + oldLatex.substring(end);
245
+ end = start;
246
+ }
247
+
248
+ // Remove any extra marks after the first mark
249
+ insert = insert.replace("#", MARK);
250
+ insert = insert.replaceAll("#", "");
251
+ insert = insert.replace(MARK, "#");
252
+
253
+ // Construct new latex
254
+ let newLatex =
255
+ oldLatex.substring(0, start) +
256
+ insert +
257
+ MARK +
258
+ oldLatex.substring(start);
259
+ let newSelectionStart = newLatex.indexOf(MARK);
260
+ newLatex =
261
+ newLatex.substring(0, newSelectionStart) +
262
+ newLatex.substring(newSelectionStart + MARK.length);
263
+
264
+ // Move cursor position back to mark in inserted text if applicable
265
+ pos = insert.indexOf("#");
266
+ if (pos > 0) {
267
+ newSelectionStart = newLatex.lastIndexOf("#", newSelectionStart);
268
+ newLatex =
269
+ newLatex.substring(0, newSelectionStart) +
270
+ newLatex.substring(newSelectionStart + 1);
271
+ }
272
+
273
+ this.setState({ latex: newLatex }, () => {
274
+ this.latexInput.selectionStart = newSelectionStart;
275
+ this.latexInput.selectionEnd = newSelectionStart;
276
+ this.latexInput.focus();
277
+ });
278
+ } catch (error) {
279
+ console.error(error);
280
+ // console.log("Inserting: " + symbol);
281
+ // console.log("into: " + this.latexInput.value);
282
+ // console.log("at: " + start + ":" + end);
152
283
  }
284
+ };
153
285
 
154
- handleInsertSymbol = (symbol) => {
155
- if (this.props.useExpertMode) this.handleInsertSymbolKatex(symbol);
156
- else this.handleInsertSymbolMathQuill(symbol);
157
- };
158
-
159
- isLetter = (char) => {
160
- return ("a" <= char && char <= "z") || ("A" <= char && char <= "Z");
161
- };
162
-
163
- // Returns:
164
- // 1. the provided pos if pos is not inside a latex tag
165
- // 2. a new pos if pos is close to the start or end of a tag (within 2 characters)
166
- // 3. -1 if pos is not close to the start or end of a tag
167
- movePosIfInsideTag(latex, pos) {
168
- if (pos === 0 || pos === latex.length) return pos;
286
+ handleInsertSymbolMathQuill = (symbol) => {
287
+ try {
288
+ this.updateRecentSymbols(symbol);
169
289
 
170
- let c = latex.charAt(pos);
171
- if (!this.isLetter(c)) return pos;
172
- let cm1 = latex.charAt(pos - 1);
173
- if (!this.isLetter(cm1) && cm1 !== "\\") return pos - 1;
290
+ let insert = getLatexInsertionParams(symbol);
291
+ if (insert === null || insert === undefined) {
292
+ console.err(
293
+ "Could not get latex insertion parameters for symbol: " + symbol
294
+ );
295
+ this.mathQuill.focus();
296
+ return;
297
+ }
174
298
 
175
- // We are inside a latex tag, let's move out of it
176
- if (pos === 1) return 0;
177
- if (pos === latex.length - 1) return latex.length;
299
+ if (insert.typedText) this.mathQuill.typedText(insert.mathQuillLatex);
300
+ else this.mathQuill.write(insert.mathQuillLatex);
178
301
 
179
- if (latex.charAt(pos - 1) === "\\") return pos - 1;
180
- if (!this.isLetter(latex.charAt(pos + 1))) return pos + 1;
181
- if (latex.charAt(pos - 2) === "\\") return pos - 2;
182
- if (!this.isLetter(latex.charAt(pos + 2))) return pos + 2;
302
+ this.mathQuill.focus();
183
303
 
184
- return -1;
304
+ for (let i = 0; i < insert.moveCursor; i++)
305
+ this.mathQuill.keystroke("Right");
306
+ for (let i = 0; i > insert.moveCursor; i--)
307
+ this.mathQuill.keystroke("Left");
308
+ } catch (error) {
309
+ console.error(error);
310
+ console.log("Symbol: " + symbol);
185
311
  }
186
-
187
- handleInsertSymbolKatex = (symbol) => {
188
- let oldLatex = null;
189
- let start = -1;
190
- let end = -1;
191
- try {
192
- this.updateRecentSymbols(symbol);
193
- oldLatex = this.latexInput.value;
194
- start = this.latexInput.selectionStart;
195
- end = this.latexInput.selectionEnd;
196
-
197
- start = this.movePosIfInsideTag(oldLatex, start);
198
- end = this.movePosIfInsideTag(oldLatex, end);
199
- if (start === -1 || end === -1) {
200
- console.log(
201
- "Can't insert as cursor position or start or end of selection is inside a tag"
202
- );
203
- return;
204
- }
205
-
206
- // if (this.latexInput !== document.activeElement)
207
- this.latexInput.focus();
208
-
209
- let insertParams = getLatexInsertionParams(symbol);
210
- let insert = insertParams.latexWithMarks;
211
- // console.log("Insert params: ");
212
- // console.log(insertParams);
213
- // console.log(start + ":" + end);
214
- // console.log(oldLatex);
215
-
216
- // Move text before cursor into latex if double mark exists in latex
217
- let pos = insert.indexOf("##");
218
- if (pos >= 0) {
219
- if (start !== end) {
220
- insert = insert.replace("##", oldLatex.substring(start, end));
221
- // console.log("insert: " + insert);
222
- oldLatex = oldLatex.substring(0, start) + oldLatex.substring(end);
223
- end = start;
224
- } else if (start > 0) {
225
- insert = insert.replace("##", oldLatex.charAt(start - 1));
226
- oldLatex = oldLatex.substring(0, start - 1) + oldLatex.substring(start);
227
- start = start - 1;
228
- end = start;
229
- } else {
230
- insert = insert.replace("##", "#");
231
- }
232
- }
233
- // Move text before cursor into latex if selection made and single mark exists in latex
234
- // or otherwise delete selected text
235
- if (start !== end) {
236
- pos = insert.indexOf("#");
237
- if (pos >= 0) insert = insert.replace("#", oldLatex.substring(start, end));
238
-
239
- oldLatex = oldLatex.substring(0, start) + oldLatex.substring(end);
240
- end = start;
241
- }
242
-
243
- // Remove any extra marks after the first mark
244
- insert = insert.replace("#", MARK);
245
- insert = insert.replaceAll("#", "");
246
- insert = insert.replace(MARK, "#");
247
-
248
- // Construct new latex
249
- let newLatex = oldLatex.substring(0, start) + insert + MARK + oldLatex.substring(start);
250
- let newSelectionStart = newLatex.indexOf(MARK);
251
- newLatex =
252
- newLatex.substring(0, newSelectionStart) +
253
- newLatex.substring(newSelectionStart + MARK.length);
254
-
255
- // Move cursor position back to mark in inserted text if applicable
256
- pos = insert.indexOf("#");
257
- if (pos > 0) {
258
- newSelectionStart = newLatex.lastIndexOf("#", newSelectionStart);
259
- newLatex =
260
- newLatex.substring(0, newSelectionStart) +
261
- newLatex.substring(newSelectionStart + 1);
262
- }
263
-
264
- this.setState({ latex: newLatex }, () => {
265
- this.latexInput.selectionStart = newSelectionStart;
266
- this.latexInput.selectionEnd = newSelectionStart;
267
- this.latexInput.focus();
268
- });
269
- } catch (error) {
270
- console.error(error);
271
- // console.log("Inserting: " + symbol);
272
- // console.log("into: " + this.latexInput.value);
273
- // console.log("at: " + start + ":" + end);
274
- }
275
- };
276
-
277
- handleInsertSymbolMathQuill = (symbol) => {
278
- try {
279
- this.updateRecentSymbols(symbol);
280
-
281
- let insert = getLatexInsertionParams(symbol);
282
- if (insert === null || insert === undefined) {
283
- console.err("Could not get latex insertion parameters for symbol: " + symbol);
284
- this.mathQuill.focus();
285
- return;
286
- }
287
-
288
- if (insert.typedText) this.mathQuill.typedText(insert.mathQuillLatex);
289
- else this.mathQuill.write(insert.mathQuillLatex);
290
-
291
- this.mathQuill.focus();
292
-
293
- for (let i = 0; i < insert.moveCursor; i++) this.mathQuill.keystroke("Right");
294
- for (let i = 0; i > insert.moveCursor; i--) this.mathQuill.keystroke("Left");
295
- } catch (error) {
296
- console.error(error);
297
- console.log("Symbol: " + symbol);
312
+ };
313
+
314
+ handleSelectTab = (tab) => {
315
+ if (!this.props.useExpertMode) this.mathQuill.focus();
316
+ else this.latexInput.focus();
317
+
318
+ GLOBAL_last_selected_tab = tab;
319
+ this.setState({ selectedTab: tab }, () => {
320
+ let buttonAreas = this.symbolButtonAreasInnerContainer.childNodes;
321
+ for (let i = 0; i < buttonAreas.length; i++) {
322
+ if (buttonAreas[i].id === TAB_STARTS_AT_AREA_NAMED[tab]) {
323
+ buttonAreas[i].scrollIntoView({
324
+ behavior: "smooth",
325
+ inline: "start",
326
+ });
298
327
  }
328
+ }
329
+ });
330
+ };
331
+
332
+ mathquillDidMount(mathQuill) {
333
+ this.mathQuill = mathQuill;
334
+ }
335
+
336
+ componentDidMount() {
337
+ // let thisComponentNode = ReactDOM.findDOMNode(this);
338
+ this.equationEditorDiv.addEventListener("keydown", this.handleKeyDown);
339
+
340
+ // Restore scroll position for tab to last selected
341
+ let buttonAreas = this.symbolButtonAreasInnerContainer.childNodes;
342
+ for (let i = 0; i < buttonAreas.length; i++) {
343
+ if (
344
+ buttonAreas[i].id === TAB_STARTS_AT_AREA_NAMED[GLOBAL_last_selected_tab]
345
+ ) {
346
+ buttonAreas[i].scrollIntoView({ behavior: "auto", inline: "start" });
347
+ }
348
+ }
349
+ }
350
+
351
+ componentWillUnmount() {
352
+ // let thisComponentNode = ReactDOM.findDOMNode(this);
353
+ this.equationEditorDiv.removeEventListener("keydown", this.handleKeyDown);
354
+ }
355
+
356
+ isMobile() {
357
+ return (
358
+ (window.innerHeight > window.innerWidth && window.innerHeight < 820) ||
359
+ window.innerHeight < 500
360
+ );
361
+ }
362
+
363
+ isTouchDevice() {
364
+ return (
365
+ "ontouchstart" in window ||
366
+ navigator.maxTouchPoints > 0 ||
367
+ navigator.msMaxTouchPoints > 0
368
+ );
369
+ }
370
+
371
+ handleCursorLeft = () => {
372
+ this.mathQuill.keystroke("Left");
373
+ this.mathQuill.focus();
374
+ };
375
+
376
+ handleCursorRight = () => {
377
+ this.mathQuill.keystroke("Right");
378
+ this.mathQuill.focus();
379
+ };
380
+
381
+ handleLatexInputChange = (event) => {
382
+ this.setState({ latex: event.target.value, pretty: true });
383
+ };
384
+
385
+ renderLatexString = (latex) => {
386
+ const options = {
387
+ children: "",
388
+ displayMode: false,
389
+ output: "htmlAndMathml",
390
+ leqno: false,
391
+ fleqn: false,
392
+ throwOnError: false,
393
+ errorColor: "#cc0000",
394
+ macros: {},
395
+ minRuleThickness: 0,
396
+ colorIsTextColor: false,
397
+ strict: "warn",
398
+ trust: false,
299
399
  };
300
400
 
301
- handleSelectTab = (tab) => {
302
- if (!this.props.useExpertMode) this.mathQuill.focus();
303
- else this.latexInput.focus();
304
-
305
- GLOBAL_last_selected_tab = tab;
306
- this.setState({ selectedTab: tab }, () => {
307
- let buttonAreas = this.symbolButtonAreasInnerContainer.childNodes;
308
- for (let i = 0; i < buttonAreas.length; i++) {
309
- if (buttonAreas[i].id === TAB_STARTS_AT_AREA_NAMED[tab]) {
310
- buttonAreas[i].scrollIntoView({
311
- behavior: "smooth",
312
- inline: "start",
313
- });
314
- }
315
- }
316
- });
317
- };
318
-
319
- mathquillDidMount(mathQuill) {
320
- this.mathQuill = mathQuill;
321
- mathQuill.focus();
401
+ try {
402
+ // returns HTML markup
403
+ return katex.renderToString(latex, options);
404
+ } catch (err) {
405
+ console.error("Could not convert latex string to html: ", latex);
406
+ return "Error: Unable to render latex";
322
407
  }
323
-
324
- componentDidMount() {
325
- // let thisComponentNode = ReactDOM.findDOMNode(this);
326
- this.equationEditorDiv.addEventListener("keydown", this.handleKeyDown);
327
-
328
- // Restore scroll position for tab to last selected
329
- let buttonAreas = this.symbolButtonAreasInnerContainer.childNodes;
330
- for (let i = 0; i < buttonAreas.length; i++) {
331
- if (buttonAreas[i].id === TAB_STARTS_AT_AREA_NAMED[GLOBAL_last_selected_tab]) {
332
- buttonAreas[i].scrollIntoView({ behavior: "auto", inline: "start" });
333
- }
334
- }
408
+ };
409
+
410
+ render() {
411
+ let katexHtml = null;
412
+ let prettyLatex = null;
413
+ if (this.props.useExpertMode) {
414
+ katexHtml = this.renderLatexString(this.state.latex);
415
+ if (this.state.pretty) prettyLatex = this.state.latex;
416
+ else {
417
+ prettyLatex = this.prettifyLatex(this.state.latex);
418
+ }
335
419
  }
336
420
 
337
- componentWillUnmount() {
338
- // let thisComponentNode = ReactDOM.findDOMNode(this);
339
- this.equationEditorDiv.removeEventListener("keydown", this.handleKeyDown);
421
+ let tabNames = TAB_NAMES;
422
+ let buttonAreaNames = BUTTON_AREA_NAMES;
423
+ let buttonAreaWidths = GLOBAL_buttonAreasWidths;
424
+ if (this.props.useExpertMode) {
425
+ tabNames = TAB_NAMES_EXPERT;
426
+ buttonAreaNames = BUTTON_AREA_NAMES_EXPERT;
427
+ buttonAreaWidths = GLOBAL_buttonAreasWidthsExpert;
340
428
  }
341
429
 
342
- isMobile() {
430
+ const recentButtons = GLOBAL_recentSymbols.slice(0, RECENT_MAX).map(
431
+ (symbol, index) => {
343
432
  return (
344
- (window.innerHeight > window.innerWidth && window.innerHeight < 820) ||
345
- window.innerHeight < 500
433
+ <SymbolButton
434
+ className="SymbolButton"
435
+ key={"recent=symbol-" + index}
436
+ symbol={symbol.name}
437
+ shouldUpdate={true}
438
+ highlight={
439
+ symbol.name === GLOBAL_lastSymbols[0]
440
+ ? "0"
441
+ : symbol.name === GLOBAL_lastSymbols[1]
442
+ ? "1"
443
+ : symbol.name === GLOBAL_lastSymbols[2]
444
+ ? "2"
445
+ : "none"
446
+ }
447
+ handleClick={(latex) => this.handleInsertSymbol(symbol.name)}
448
+ />
346
449
  );
450
+ }
451
+ );
452
+
453
+ if (buttonAreaWidths === null) {
454
+ let widths = [];
455
+ for (let i = 0; i < buttonAreaNames.length; i++) {
456
+ widths[i] = Math.ceil(
457
+ getSymbolsForButtonArea(buttonAreaNames[i]).length / MAX_BUTTON_ROWS
458
+ );
459
+ }
460
+ buttonAreaWidths = widths;
347
461
  }
348
462
 
349
- isTouchDevice() {
463
+ const buttonAreasButtons = buttonAreaNames.map((buttonArea, index) => {
464
+ return getSymbolsForButtonArea(buttonArea).map((symbol, index) => {
350
465
  return (
351
- "ontouchstart" in window ||
352
- navigator.maxTouchPoints > 0 ||
353
- navigator.msMaxTouchPoints > 0
466
+ <SymbolButton
467
+ className="SymbolButton"
468
+ key={buttonArea + "-symbol-" + index}
469
+ highlight={buttonArea.toLowerCase()}
470
+ symbol={symbol}
471
+ handleClick={() => this.handleInsertSymbol(symbol)}
472
+ />
354
473
  );
355
- }
356
-
357
- handleCursorLeft = () => {
358
- this.mathQuill.keystroke("Left");
359
- this.mathQuill.focus();
360
- };
361
-
362
- handleCursorRight = () => {
363
- this.mathQuill.keystroke("Right");
364
- this.mathQuill.focus();
365
- };
366
-
367
- handleLatexInputChange = (event) => {
368
- this.setState({ latex: event.target.value, pretty: true });
369
- };
370
-
371
- renderLatexString = (latex) => {
372
- const options = {
373
- children: "",
374
- displayMode: false,
375
- output: "htmlAndMathml",
376
- leqno: false,
377
- fleqn: false,
378
- throwOnError: false,
379
- errorColor: "#cc0000",
380
- macros: {},
381
- minRuleThickness: 0,
382
- colorIsTextColor: false,
383
- strict: "warn",
384
- trust: false,
385
- };
386
-
387
- try {
388
- // returns HTML markup
389
- return katex.renderToString(latex, options);
390
- } catch (err) {
391
- console.error("Could not convert latex string to html: ", latex);
392
- return "Error: Unable to render latex";
393
- }
394
- };
395
-
396
- render() {
397
- let katexHtml = null;
398
- let prettyLatex = null;
399
- if (this.props.useExpertMode) {
400
- katexHtml = this.renderLatexString(this.state.latex);
401
- if (this.state.pretty) prettyLatex = this.state.latex;
402
- else {
403
- prettyLatex = this.prettifyLatex(this.state.latex);
404
- }
405
- }
474
+ });
475
+ });
406
476
 
407
- let tabNames = TAB_NAMES;
408
- let buttonAreaNames = BUTTON_AREA_NAMES;
409
- let buttonAreaWidths = GLOBAL_buttonAreasWidths;
410
- if (this.props.useExpertMode) {
411
- tabNames = TAB_NAMES_EXPERT;
412
- buttonAreaNames = BUTTON_AREA_NAMES_EXPERT;
413
- buttonAreaWidths = GLOBAL_buttonAreasWidthsExpert;
414
- }
415
-
416
- const recentButtons = GLOBAL_recentSymbols.slice(0, RECENT_MAX).map((symbol, index) => {
417
- return (
418
- <SymbolButton
419
- className="SymbolButton"
420
- key={"recent=symbol-" + index}
421
- symbol={symbol.name}
422
- shouldUpdate={true}
423
- highlight={
424
- symbol.name === GLOBAL_lastSymbols[0]
425
- ? "0"
426
- : symbol.name === GLOBAL_lastSymbols[1]
427
- ? "1"
428
- : symbol.name === GLOBAL_lastSymbols[2]
429
- ? "2"
430
- : "none"
431
- }
432
- handleClick={(latex) => this.handleInsertSymbol(symbol.name)}
433
- />
434
- );
435
- });
436
-
437
- if (buttonAreaWidths === null) {
438
- let widths = [];
439
- for (let i = 0; i < buttonAreaNames.length; i++) {
440
- widths[i] = Math.ceil(
441
- getSymbolsForButtonArea(buttonAreaNames[i]).length / MAX_BUTTON_ROWS
442
- );
443
- }
444
- buttonAreaWidths = widths;
445
- }
446
-
447
- const buttonAreasButtons = buttonAreaNames.map((buttonArea, index) => {
448
- return getSymbolsForButtonArea(buttonArea).map((symbol, index) => {
449
- return (
450
- <SymbolButton
451
- className="SymbolButton"
452
- key={buttonArea + "-symbol-" + index}
453
- highlight={buttonArea.toLowerCase()}
454
- symbol={symbol}
455
- handleClick={() => this.handleInsertSymbol(symbol)}
456
- />
457
- );
458
- });
459
- });
460
-
461
- return (
462
- <div className="EquationEditor" ref={(r) => (this.equationEditorDiv = r)}>
463
- {/* Normal mode. Add cursor move buttons for mobile devices as they don't have
477
+ return (
478
+ <div className="EquationEditor" ref={(r) => (this.equationEditorDiv = r)}>
479
+ {/* Normal mode. Add cursor move buttons for mobile devices as they don't have
464
480
  cursor keys in the soft keyboard. */}
465
- {!this.props.useExpertMode && (
466
- <div className="editableAndCursorButtonsWrapper">
467
- {this.isTouchDevice() && (
468
- <button
469
- className="cursorButton"
470
- onClick={(t) => this.handleCursorLeft()}
471
- >
472
- {"\u25C1"}
473
- </button>
474
- )}
475
-
476
- <div ref={(r) => (this.editableWrapper = r)} className="editableWrapper">
477
- <div className="centerBox">
478
- <EditableMathField
479
- className="editable"
480
- latex={this.state.latex}
481
- placeholder="Type equation .."
482
- config={{
483
- autoOperatorNames: AUTO_OPERATOR_NAMES,
484
- }}
485
- mathquillDidMount={(mathQuill) =>
486
- this.mathquillDidMount(mathQuill)
487
- }
488
- onChange={(mathQuill) => this.handleChange(mathQuill)}
489
- />
490
- {this.state.latex.length === 0 && (
491
- <div className="editable-placeholder">Type equation</div>
492
- )}
493
- </div>
494
- </div>
495
-
496
- {this.isTouchDevice() && (
497
- <button
498
- className="cursorButton"
499
- onClick={(t) => this.handleCursorRight()}
500
- >
501
- {"\u25B7"}
502
- </button>
503
- )}
504
- </div>
505
- )}
506
-
507
- {/* Expert mode */}
508
- {this.props.useExpertMode && (
509
- <div className="latexInputAreaWrapper">
510
- <textarea
511
- className="latexInput"
512
- autoFocus
513
- placeholder="Type latex"
514
- spellCheck="false"
515
- ref={(r) => (this.latexInput = r)}
516
- onChange={this.handleLatexInputChange}
517
- value={prettyLatex}
518
- ></textarea>
519
- <div
520
- className="katexRenderedInput"
521
- dangerouslySetInnerHTML={{ __html: katexHtml }}
522
- />
523
- </div>
481
+ {!this.props.useExpertMode && (
482
+ <div className="editableAndCursorButtonsWrapper">
483
+ {this.isTouchDevice() && (
484
+ <button
485
+ className="cursorButton"
486
+ onClick={(t) => this.handleCursorLeft()}
487
+ >
488
+ {"\u25C1"}
489
+ </button>
490
+ )}
491
+
492
+ <div
493
+ ref={(r) => (this.editableWrapper = r)}
494
+ className="editableWrapper"
495
+ >
496
+ <div className="centerBox">
497
+ <EditableMathField
498
+ className="editable"
499
+ latex={this.state.latex}
500
+ placeholder="Type equation .."
501
+ config={{
502
+ autoOperatorNames: AUTO_OPERATOR_NAMES,
503
+ }}
504
+ mathquillDidMount={(mathQuill) =>
505
+ this.mathquillDidMount(mathQuill)
506
+ }
507
+ onChange={(mathQuill) => this.handleChange(mathQuill)}
508
+ />
509
+ {this.state.latex.length === 0 && (
510
+ <div className="editable-placeholder">Type equation</div>
524
511
  )}
512
+ </div>
513
+ </div>
525
514
 
526
- {/* Cancel and submit button row. Cancel button is only shown on mobile devices as
515
+ {this.isTouchDevice() && (
516
+ <button
517
+ className="cursorButton"
518
+ onClick={(t) => this.handleCursorRight()}
519
+ >
520
+ {"\u25B7"}
521
+ </button>
522
+ )}
523
+ </div>
524
+ )}
525
+
526
+ {/* Expert mode */}
527
+ {this.props.useExpertMode && (
528
+ <div className="latexInputAreaWrapper">
529
+ <textarea
530
+ className="latexInput"
531
+ autoFocus
532
+ placeholder="Type latex"
533
+ spellCheck="false"
534
+ ref={(r) => (this.latexInput = r)}
535
+ onChange={this.handleLatexInputChange}
536
+ value={prettyLatex}
537
+ ></textarea>
538
+ <div
539
+ className="katexRenderedInput"
540
+ dangerouslySetInnerHTML={{ __html: katexHtml }}
541
+ />
542
+ </div>
543
+ )}
544
+
545
+ {/* Cancel and submit button row. Cancel button is only shown on mobile devices as
527
546
  there may be no visible background to click on to close the modal. */}
528
- {this.isMobile() && (
529
- <button className="cancelButton" type="button" onClick={this.handleCancel}>
530
- Cancel
531
- </button>
532
- )}
533
- <button className="insertButton" type="button" onClick={this.handleInsert}>
534
- Insert
535
- </button>
536
-
537
- {/* Recent symbols button bar */}
538
- {!this.isMobile() && (
539
- <div className="recentSymbolsButtonArea">
540
- {GLOBAL_recentSymbols.length > 0 && GLOBAL_recentSymbols.length < 6 && (
541
- <span className="recentSymbols-recentText-active">Recent:</span>
542
- )}
543
- {GLOBAL_recentSymbols.length === 0 && (
544
- <span className="recentSymbols-recentText-not-active">Recent:</span>
545
- )}
546
- {recentButtons}
547
- </div>
548
- )}
549
-
550
- {/* Symbol buttons area */}
551
- <div>
552
- <div className="tabBar">
553
- {tabNames.map((tab, index) => {
554
- return (
555
- <span key={"tabBar-" + tab}>
556
- {this.state.selectedTab === tab ? (
557
- <span className="tab-selected" selected={true}>
558
- {tab}
559
- </span>
560
- ) : (
561
- <span
562
- className="tab"
563
- onClick={(t) => this.handleSelectTab(tab)}
564
- >
565
- {tab}
566
- </span>
567
- )}{" "}
568
- </span>
569
- );
570
- })}
571
- </div>
572
-
573
- <div className="symbolButtonAreasOuterContainer">
574
- <div
575
- className="symbolButtonAreasInnerContainer"
576
- ref={(r) => (this.symbolButtonAreasInnerContainer = r)}
577
- >
578
- {buttonAreasButtons.map((button, index) => {
579
- return (
580
- <div
581
- id={buttonAreaNames[index]}
582
- key={buttonAreaNames[index]}
583
- className={
584
- "symbolButtonArea symbolButtonArea-" +
585
- buttonAreaWidths[index]
586
- }
587
- >
588
- {button}
589
- </div>
590
- );
591
- })}
592
- </div>
593
- </div>
594
- </div>
547
+ {this.isMobile() && (
548
+ <button
549
+ className="cancelButton"
550
+ type="button"
551
+ onClick={this.handleCancel}
552
+ >
553
+ Cancel
554
+ </button>
555
+ )}
556
+ <button
557
+ className="insertButton"
558
+ type="button"
559
+ onClick={this.handleInsert}
560
+ >
561
+ Insert
562
+ </button>
563
+
564
+ {/* Recent symbols button bar */}
565
+ {!this.isMobile() && (
566
+ <div className="recentSymbolsButtonArea">
567
+ {GLOBAL_recentSymbols.length > 0 &&
568
+ GLOBAL_recentSymbols.length < 6 && (
569
+ <span className="recentSymbols-recentText-active">Recent:</span>
570
+ )}
571
+ {GLOBAL_recentSymbols.length === 0 && (
572
+ <span className="recentSymbols-recentText-not-active">
573
+ Recent:
574
+ </span>
575
+ )}
576
+ {recentButtons}
577
+ </div>
578
+ )}
579
+
580
+ {/* Symbol buttons area */}
581
+ <div>
582
+ <div className="tabBar">
583
+ {tabNames.map((tab, index) => {
584
+ return (
585
+ <span key={"tabBar-" + tab}>
586
+ {this.state.selectedTab === tab ? (
587
+ <span className="tab-selected" selected={true}>
588
+ {tab}
589
+ </span>
590
+ ) : (
591
+ <span
592
+ className="tab"
593
+ onClick={(t) => this.handleSelectTab(tab)}
594
+ >
595
+ {tab}
596
+ </span>
597
+ )}{" "}
598
+ </span>
599
+ );
600
+ })}
601
+ </div>
602
+
603
+ <div className="symbolButtonAreasOuterContainer">
604
+ <div
605
+ className="symbolButtonAreasInnerContainer"
606
+ ref={(r) => (this.symbolButtonAreasInnerContainer = r)}
607
+ >
608
+ {buttonAreasButtons.map((button, index) => {
609
+ return (
610
+ <div
611
+ id={buttonAreaNames[index]}
612
+ key={buttonAreaNames[index]}
613
+ className={
614
+ "symbolButtonArea symbolButtonArea-" +
615
+ buttonAreaWidths[index]
616
+ }
617
+ >
618
+ {button}
619
+ </div>
620
+ );
621
+ })}
595
622
  </div>
596
- );
597
- }
623
+ </div>
624
+ </div>
625
+ </div>
626
+ );
627
+ }
598
628
  }