@zzish/math-rich-input 0.1.41 → 0.1.43

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