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