@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.
- package/dist/index.js +937 -1536
- package/dist/math-rich-input.css +594 -564
- package/package.json +3 -3
- package/src/AccentBar.css +113 -111
- package/src/EquationEditor.css +160 -159
- package/src/EquationEditor.jsx +528 -560
- package/src/EquationEditorModal.css +109 -101
- package/src/EquationEditorModal.jsx +65 -68
- package/src/MathRichInput.css +87 -69
- package/src/MathRichInput.jsx +1981 -2070
- package/src/Toolbar.css +83 -82
- package/src/Toolbar.jsx +220 -234
- package/src/index.js +4 -8
package/src/EquationEditor.jsx
CHANGED
|
@@ -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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
139
|
+
if (recentSymbols.length >= RECENT_MAX) {
|
|
140
|
+
recentSymbols.splice(minIndex, 1);
|
|
141
|
+
}
|
|
142
|
+
recentSymbols.push({ name: symbol, count: RECENT_WEIGHT });
|
|
236
143
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
154
|
+
handleInsertSymbol = (symbol) => {
|
|
155
|
+
if (this.props.useExpertMode) this.handleInsertSymbolKatex(symbol);
|
|
156
|
+
else this.handleInsertSymbolMathQuill(symbol);
|
|
157
|
+
};
|
|
290
158
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
433
|
-
(symbol, index) => {
|
|
342
|
+
isMobile() {
|
|
434
343
|
return (
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
466
|
-
return getSymbolsForButtonArea(buttonArea).map((symbol, index) => {
|
|
349
|
+
isTouchDevice() {
|
|
467
350
|
return (
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
</div>
|
|
628
|
-
);
|
|
629
|
-
}
|
|
596
|
+
);
|
|
597
|
+
}
|
|
630
598
|
}
|