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