@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/MathRichInput.jsx
CHANGED
|
@@ -3,31 +3,31 @@ import React from "react";
|
|
|
3
3
|
import katex from "katex";
|
|
4
4
|
import { setupKatex } from "./katexHelper";
|
|
5
5
|
|
|
6
|
-
import EquationEditorModal from "./EquationEditorModal";
|
|
7
|
-
import Toolbar from "./Toolbar";
|
|
6
|
+
import EquationEditorModal from "./EquationEditorModal.jsx";
|
|
7
|
+
import Toolbar from "./Toolbar.jsx";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
10
|
+
getMark,
|
|
11
|
+
isKatexNode,
|
|
12
|
+
elementToMarkedRawText,
|
|
13
|
+
removeEncodingIfPlainText,
|
|
14
|
+
rawTextToHtml,
|
|
15
|
+
getLatex,
|
|
16
|
+
setRangeParams,
|
|
17
|
+
getRangeParams,
|
|
18
|
+
getNodeTextLength,
|
|
19
|
+
findNodeWithIndex,
|
|
20
|
+
findIndexOfNode,
|
|
21
|
+
findFirstTextNode,
|
|
22
|
+
countNodeTypes,
|
|
23
|
+
startsWithSmallSpace,
|
|
24
|
+
getCharacterBeforeMarks,
|
|
25
|
+
replaceCharacterBeforeMarks,
|
|
26
|
+
replaceStringBeforeMarks,
|
|
27
|
+
insertCharacterBeforeMarks,
|
|
28
|
+
removeMarks,
|
|
29
|
+
replaceMarksWithMath,
|
|
30
|
+
sanitiseHtml,
|
|
31
31
|
} from "./mathRichInputHelper";
|
|
32
32
|
|
|
33
33
|
import "./MathRichInput.css";
|
|
@@ -52,2193 +52,2104 @@ const MARK_REG_EXP = new RegExp(MARK, "g");
|
|
|
52
52
|
const MAX_HISTORY_LENGTH = 100;
|
|
53
53
|
|
|
54
54
|
const DEFAULT_OPTIONS = {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
useExpertMode: false,
|
|
56
|
+
selectedTab: null,
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
// Consts for creating accented letters in handleKeyDownAccentHelper
|
|
60
60
|
const GRAVE_LETTERS = {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
a: "à",
|
|
62
|
+
e: "è",
|
|
63
|
+
i: "ì",
|
|
64
|
+
o: "ò",
|
|
65
|
+
u: "ù",
|
|
66
|
+
d: "ð",
|
|
67
|
+
A: "À",
|
|
68
|
+
E: "È",
|
|
69
|
+
I: "Ì",
|
|
70
|
+
O: "Ò",
|
|
71
|
+
U: "Ù",
|
|
72
|
+
D: "Ð",
|
|
73
73
|
};
|
|
74
74
|
const ACCUTE_LETTERS = {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
75
|
+
a: "á",
|
|
76
|
+
e: "é",
|
|
77
|
+
i: "í",
|
|
78
|
+
n: "ń",
|
|
79
|
+
o: "ó",
|
|
80
|
+
u: "ú",
|
|
81
|
+
y: "ý",
|
|
82
|
+
A: "Á",
|
|
83
|
+
E: "É",
|
|
84
|
+
I: "Í",
|
|
85
|
+
N: "Ń",
|
|
86
|
+
O: "Ó",
|
|
87
|
+
U: "Ú",
|
|
88
|
+
Y: "Ý",
|
|
89
89
|
};
|
|
90
90
|
const CARET_LETTERS = {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
a: "â",
|
|
92
|
+
e: "ê",
|
|
93
|
+
i: "î",
|
|
94
|
+
o: "ô",
|
|
95
|
+
u: "û",
|
|
96
|
+
A: "Â",
|
|
97
|
+
E: "Ê",
|
|
98
|
+
I: "Î",
|
|
99
|
+
O: "Ô",
|
|
100
|
+
U: "Û",
|
|
101
101
|
};
|
|
102
102
|
const TILDE_LETTERS = { a: "ã", n: "ñ", o: "õ", A: "Ã", N: "Ñ", O: "Õ" };
|
|
103
103
|
const COLON_LETTERS = {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
104
|
+
a: "ä",
|
|
105
|
+
e: "ë",
|
|
106
|
+
i: "ï",
|
|
107
|
+
o: "ö",
|
|
108
|
+
u: "ü",
|
|
109
|
+
y: "ÿ",
|
|
110
|
+
A: "Ä",
|
|
111
|
+
E: "Ë",
|
|
112
|
+
I: "Ï",
|
|
113
|
+
O: "Ö",
|
|
114
|
+
U: "Ü",
|
|
115
|
+
Y: "Ÿ",
|
|
116
116
|
};
|
|
117
117
|
const AT_LETTERS = { a: "å", A: "Å" };
|
|
118
118
|
const AND_LETTERS = { a: "æ", o: "œ", s: "ß", A: "Æ", O: "Œ", S: "ß" };
|
|
119
119
|
const COMMA_LETTERS = { c: "ç", C: "Ç" };
|
|
120
120
|
const SLASH_LETTERS = { o: "ø", O: "Ø" };
|
|
121
121
|
const MINUS_LETTERS = {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
122
|
+
a: "ā",
|
|
123
|
+
e: "ē",
|
|
124
|
+
i: "ī",
|
|
125
|
+
o: "ō",
|
|
126
|
+
u: "ū",
|
|
127
|
+
A: "Ā",
|
|
128
|
+
E: "Ē",
|
|
129
|
+
I: "Ī",
|
|
130
|
+
O: "Ō",
|
|
131
|
+
U: "Ū",
|
|
132
132
|
};
|
|
133
133
|
const CTRL_ALT_SHIFT_LETTERS = { "?": "¿", "!": "¡", "/": "¿", 1: "¡" }; // Necessary
|
|
134
134
|
const LETTERS = {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
"`": GRAVE_LETTERS,
|
|
136
|
+
"'": ACCUTE_LETTERS,
|
|
137
|
+
'"': ACCUTE_LETTERS, // Necessary
|
|
138
|
+
"^": CARET_LETTERS,
|
|
139
|
+
6: CARET_LETTERS,
|
|
140
|
+
"~": TILDE_LETTERS,
|
|
141
|
+
":": COLON_LETTERS,
|
|
142
|
+
"@": AT_LETTERS,
|
|
143
|
+
2: AT_LETTERS,
|
|
144
|
+
"&": AND_LETTERS,
|
|
145
|
+
7: AND_LETTERS, // Necessary
|
|
146
|
+
",": COMMA_LETTERS,
|
|
147
|
+
"/": SLASH_LETTERS,
|
|
148
|
+
"-": MINUS_LETTERS,
|
|
149
149
|
};
|
|
150
150
|
const ACCENT_KEYS = ["`", "'", "^", "~", ":", "@", "&", ",", "/", "-"];
|
|
151
151
|
const CTRL_ALT_SHIFT_KEYS = ["?", "!"];
|
|
152
152
|
// End of consts for creating accented letters in handleKeyDownAccentHelper
|
|
153
153
|
|
|
154
154
|
export default class MathRichInput extends React.Component {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
constructor(props) {
|
|
156
|
+
super(props);
|
|
157
|
+
this.state = {
|
|
158
|
+
showEquationEditor: false,
|
|
159
|
+
equationEditorLatex: "",
|
|
160
|
+
showAccentBar: false,
|
|
161
|
+
activeButtons: {
|
|
162
|
+
bold: false,
|
|
163
|
+
italic: false,
|
|
164
|
+
underline: false,
|
|
165
|
+
subscript: false,
|
|
166
|
+
superscript: false,
|
|
167
|
+
},
|
|
168
|
+
keyPressed: "",
|
|
169
|
+
};
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
// this.handleInput = this.handleInput.bind(this);
|
|
172
|
+
// this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
173
|
+
// this.handleKeyUp = this.handleKeyUp.bind(this);
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
// Remember the last key pressed
|
|
176
|
+
this.keyPressed = null;
|
|
177
|
+
this.keyPressStartTime = -1;
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
// This gets updated in coomponent did mount, used to position the toolbar correctly
|
|
180
|
+
this.translateToolbar = "";
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
// Whether the text contains HTML or Maths
|
|
183
|
+
this.isHtml = false;
|
|
184
|
+
this.hasMath = false;
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// Elements defining the current state of range
|
|
197
|
-
this.afterComponentUpdateData = null; // Set this to update the selection after rendering
|
|
198
|
-
this.lastSetRangeParmas = null; // This is set automatically each time the range params are set with setRangeParams
|
|
199
|
-
|
|
200
|
-
this.lastSetActiveButtons = null;
|
|
201
|
-
|
|
202
|
-
this.onChangeCallback = this.props.onChange;
|
|
203
|
-
|
|
204
|
-
// set the initialHistoryState the first time an action is done that allows us to identify the cursor position
|
|
205
|
-
// 1. When the first mouse click is released (see onMouseUp)
|
|
206
|
-
// 2. When the first key is pressed (see handleKeyDown)
|
|
207
|
-
this.initialHistoryState = null;
|
|
208
|
-
this.undoHistory = [];
|
|
209
|
-
this.redoHistory = [];
|
|
210
|
-
this.accent = "";
|
|
211
|
-
|
|
212
|
-
// keeps track of whether user is composing in an IME - see https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
|
|
213
|
-
this.isComposing = false;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
enableFontStyling() {
|
|
217
|
-
if (
|
|
218
|
-
this.props.outputTex !== null &&
|
|
219
|
-
this.props.outputTex !== undefined &&
|
|
220
|
-
this.props.outputTex === true
|
|
221
|
-
)
|
|
222
|
-
return false;
|
|
223
|
-
else if (
|
|
224
|
-
this.props.enableFontStyling !== null &&
|
|
225
|
-
this.props.enableFontStyling !== undefined
|
|
226
|
-
)
|
|
227
|
-
return this.props.enableFontStyling;
|
|
228
|
-
else return true;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
enableMultiline() {
|
|
232
|
-
if (
|
|
233
|
-
this.props.outputTex !== null &&
|
|
234
|
-
this.props.outputTex !== undefined &&
|
|
235
|
-
this.props.outputTex === true
|
|
236
|
-
)
|
|
237
|
-
return false;
|
|
238
|
-
else if (
|
|
239
|
-
this.props.enableMultiline !== null &&
|
|
240
|
-
this.props.enableMultiline !== undefined
|
|
241
|
-
)
|
|
242
|
-
return this.props.enableMultiline;
|
|
243
|
-
else return true;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
enableHtml() {
|
|
247
|
-
let outputTex = this.props.outputTex ?? false;
|
|
248
|
-
return !outputTex;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
enableMath() {
|
|
252
|
-
if (this.props.enableMath !== null && this.props.enableMath !== undefined)
|
|
253
|
-
return this.props.enableMath;
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// static isHtml(text) {
|
|
258
|
-
// return /<\s*p[^>]*>(.*?)<\s*\/\s*p\s*>/i.test(text) ||
|
|
259
|
-
// /<\s*b[^>]*>(.*?)<\s*\/\s*b\s*>/i.test(text) ||
|
|
260
|
-
// /<\s*i[^>]*>(.*?)<\s*\/\s*i\s*>/i.test(text) ||
|
|
261
|
-
// /<\s*u[^>]*>(.*?)<\s*\/\s*u\s*>/i.test(text) ||
|
|
262
|
-
// /<\s*sup[^>]*>(.*?)<\s*\/\s*sup\s*>/i.test(text) ||
|
|
263
|
-
// /<\s*sub[^>]*>(.*?)<\s*\/\s*sub\s*>/i.test(text)
|
|
264
|
-
// }
|
|
265
|
-
|
|
266
|
-
getMimeType(hasHtml, hasMath) {
|
|
267
|
-
let outputTex = this.props.outputTex ?? false;
|
|
268
|
-
let outputPlainText = this.props.outputPlainText ?? false;
|
|
269
|
-
|
|
270
|
-
if (outputTex) {
|
|
271
|
-
if (hasMath || !outputPlainText) return MIME_TYPE_TEX;
|
|
272
|
-
else return MIME_TYPE_PLAIN_TEXT;
|
|
273
|
-
}
|
|
186
|
+
// Elements that need to be set before the equation editor is shown
|
|
187
|
+
this.markedRawText = null;
|
|
188
|
+
this.oldRangeParams = {
|
|
189
|
+
startNodeIndex: 0,
|
|
190
|
+
startOffset: 0,
|
|
191
|
+
endNodeIndex: 0,
|
|
192
|
+
endOffset: 0,
|
|
193
|
+
};
|
|
194
|
+
this.moveCursorOnInsert = false;
|
|
274
195
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return MIME_TYPE_PLAIN_TEXT;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
_countNodeTypes = () => {
|
|
282
|
-
return countNodeTypes(this.editableDiv);
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
applyChangesToComponent = (
|
|
286
|
-
value,
|
|
287
|
-
mimeType,
|
|
288
|
-
rangeParams,
|
|
289
|
-
isExpertMode,
|
|
290
|
-
selectedTab
|
|
291
|
-
) => {
|
|
292
|
-
|
|
293
|
-
// rangeParams, isExpertMode and selectedTab can be null, in which case we will set them
|
|
294
|
-
if (rangeParams === null) rangeParams = this.lastSetRangeParams;
|
|
295
|
-
|
|
296
|
-
this.afterComponentUpdateData = {rangeParams, value}
|
|
297
|
-
|
|
298
|
-
let options = this.props.options || DEFAULT_OPTIONS;
|
|
299
|
-
if (isExpertMode === null) isExpertMode = options.isExpertMode;
|
|
300
|
-
if (selectedTab === null) selectedTab = options.selectedTab;
|
|
301
|
-
|
|
302
|
-
// Add this to the history
|
|
303
|
-
this.undoHistory.push({ value, mimeType, rangeParams });
|
|
304
|
-
if (this.undoHistory.length > MAX_HISTORY_LENGTH) this.undoHistory.shift();
|
|
305
|
-
|
|
306
|
-
// Call the parent handler to apply the changes
|
|
307
|
-
this.onChangeCallback({ value, mimeType }, { isExpertMode, selectedTab });
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
initialiseHistory = () => {
|
|
311
|
-
if (this.initialHistoryState == null)
|
|
312
|
-
this.initialHistoryState = {
|
|
313
|
-
value: this.props.value,
|
|
314
|
-
mimeType: this.props.mimeType,
|
|
315
|
-
rangeParams: this._getRangeParams(),
|
|
316
|
-
};
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
resetHistoryRedo = () => {
|
|
320
|
-
this.redoHistory = [];
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
deepEqual = (v1, v2) => {
|
|
324
|
-
if (v1 === v2) return true;
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
v1 == null ||
|
|
328
|
-
v2 == null ||
|
|
329
|
-
typeof v1 != "object" ||
|
|
330
|
-
typeof v2 != "object"
|
|
331
|
-
)
|
|
332
|
-
return false;
|
|
333
|
-
|
|
334
|
-
let v1keys = Object.keys(v1);
|
|
335
|
-
let v2keys = Object.keys(v2);
|
|
336
|
-
if (v1keys.length !== v2keys.length) return false;
|
|
337
|
-
|
|
338
|
-
for (let key of v1keys) {
|
|
339
|
-
if (!v2keys.includes(key) || !this.deepEqual(v1[key], v2[key]))
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
return true;
|
|
343
|
-
};
|
|
196
|
+
// Elements defining the current state of range
|
|
197
|
+
this.afterComponentUpdateData = null; // Set this to update the selection after rendering
|
|
198
|
+
this.lastSetRangeParmas = null; // This is set automatically each time the range params are set with setRangeParams
|
|
344
199
|
|
|
345
|
-
|
|
346
|
-
const currentState = this.undoHistory.pop();
|
|
200
|
+
this.lastSetActiveButtons = null;
|
|
347
201
|
|
|
348
|
-
|
|
349
|
-
if (previousState === null || previousState === undefined) {
|
|
350
|
-
previousState = this.initialHistoryState;
|
|
351
|
-
}
|
|
202
|
+
this.onChangeCallback = this.props.onChange;
|
|
352
203
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
this.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
);
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
redo = () => {
|
|
367
|
-
const redoState = this.redoHistory.pop();
|
|
368
|
-
if ((redoState !== null) & (redoState !== undefined)) {
|
|
369
|
-
this.applyChangesToComponent(
|
|
370
|
-
redoState.value,
|
|
371
|
-
redoState.mimeType,
|
|
372
|
-
redoState.rangeParams
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
setOldRangeParams(params) {
|
|
378
|
-
this.oldRangeParams = params;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
getOldRangeParams() {
|
|
382
|
-
if (this.oldRangeParams === null || this.oldRangeParams === undefined)
|
|
383
|
-
return null;
|
|
384
|
-
let params = { ...this.oldRangeParams };
|
|
385
|
-
return params;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
_getRangeParams(editableDiv = null) {
|
|
389
|
-
if (editableDiv === null) editableDiv = this.editableDiv;
|
|
390
|
-
let rangeParams = getRangeParams(editableDiv);
|
|
391
|
-
return rangeParams;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
_setRangeParams = (params) => {
|
|
395
|
-
try {
|
|
396
|
-
setRangeParams(this.editableDiv, params);
|
|
397
|
-
this.setOldRangeParams(params);
|
|
398
|
-
this.lastSetRangeParmas = params;
|
|
399
|
-
} catch (error) {
|
|
400
|
-
console.error(error);
|
|
401
|
-
console.log(this.editableDiv.childNodes);
|
|
204
|
+
// set the initialHistoryState the first time an action is done that allows us to identify the cursor position
|
|
205
|
+
// 1. When the first mouse click is released (see onMouseUp)
|
|
206
|
+
// 2. When the first key is pressed (see handleKeyDown)
|
|
207
|
+
this.initialHistoryState = null;
|
|
208
|
+
this.undoHistory = [];
|
|
209
|
+
this.redoHistory = [];
|
|
210
|
+
this.accent = "";
|
|
211
|
+
|
|
212
|
+
// keeps track of whether user is composing in an IME - see https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
|
|
213
|
+
this.isComposing = false;
|
|
402
214
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
handleKeyDownBackspace = (e) => {
|
|
418
|
-
let rangeParams = this._getRangeParams();
|
|
419
|
-
let start_node_index = rangeParams.startNodeIndex;
|
|
420
|
-
let end_node_index = rangeParams.endNodeIndex;
|
|
421
|
-
let start_offset = rangeParams.startOffset;
|
|
422
|
-
let end_offset = rangeParams.endOffset;
|
|
423
|
-
|
|
424
|
-
if (start_node_index < 0)
|
|
425
|
-
// Empty input field
|
|
426
|
-
return;
|
|
427
|
-
|
|
428
|
-
let startNode = this._findNodeWithIndex(start_node_index);
|
|
429
|
-
let endNode = this._findNodeWithIndex(end_node_index);
|
|
430
|
-
|
|
431
|
-
// Prevent backspace if start or end of selected region is in a katex node
|
|
432
|
-
if (isKatexNode(startNode) || isKatexNode(endNode)) {
|
|
433
|
-
console.error("Backspace inside latex node. This shouln't happen.");
|
|
434
|
-
e.preventDefault();
|
|
215
|
+
|
|
216
|
+
enableFontStyling() {
|
|
217
|
+
if (
|
|
218
|
+
this.props.outputTex !== null &&
|
|
219
|
+
this.props.outputTex !== undefined &&
|
|
220
|
+
this.props.outputTex === true
|
|
221
|
+
)
|
|
222
|
+
return false;
|
|
223
|
+
else if (
|
|
224
|
+
this.props.enableFontStyling !== null &&
|
|
225
|
+
this.props.enableFontStyling !== undefined
|
|
226
|
+
)
|
|
227
|
+
return this.props.enableFontStyling;
|
|
228
|
+
else return true;
|
|
435
229
|
}
|
|
436
230
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
deleteToEmpty = true;
|
|
231
|
+
enableMultiline() {
|
|
232
|
+
if (
|
|
233
|
+
this.props.outputTex !== null &&
|
|
234
|
+
this.props.outputTex !== undefined &&
|
|
235
|
+
this.props.outputTex === true
|
|
236
|
+
)
|
|
237
|
+
return false;
|
|
238
|
+
else if (this.props.enableMultiline !== null && this.props.enableMultiline !== undefined)
|
|
239
|
+
return this.props.enableMultiline;
|
|
240
|
+
else return true;
|
|
448
241
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
)
|
|
454
|
-
deleteToEmpty = true;
|
|
242
|
+
|
|
243
|
+
enableHtml() {
|
|
244
|
+
let outputTex = this.props.outputTex ?? false;
|
|
245
|
+
return !outputTex;
|
|
455
246
|
}
|
|
456
247
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
startOffset: 1,
|
|
462
|
-
endNodeIndex: 0,
|
|
463
|
-
endOffset: 1,
|
|
464
|
-
startGlobalOffset: 0,
|
|
465
|
-
endGlobalOffset: 0,
|
|
466
|
-
};
|
|
467
|
-
this.applyChangesToComponent(
|
|
468
|
-
"",
|
|
469
|
-
this.getMimeType(false, false),
|
|
470
|
-
new_rangeParams,
|
|
471
|
-
this.props.useExpertMode,
|
|
472
|
-
this.props.selectedTab
|
|
473
|
-
);
|
|
474
|
-
e.preventDefault()
|
|
475
|
-
return;
|
|
248
|
+
enableMath() {
|
|
249
|
+
if (this.props.enableMath !== null && this.props.enableMath !== undefined)
|
|
250
|
+
return this.props.enableMath;
|
|
251
|
+
return true;
|
|
476
252
|
}
|
|
477
253
|
|
|
478
|
-
//
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
)
|
|
485
|
-
|
|
254
|
+
// static isHtml(text) {
|
|
255
|
+
// return /<\s*p[^>]*>(.*?)<\s*\/\s*p\s*>/i.test(text) ||
|
|
256
|
+
// /<\s*b[^>]*>(.*?)<\s*\/\s*b\s*>/i.test(text) ||
|
|
257
|
+
// /<\s*i[^>]*>(.*?)<\s*\/\s*i\s*>/i.test(text) ||
|
|
258
|
+
// /<\s*u[^>]*>(.*?)<\s*\/\s*u\s*>/i.test(text) ||
|
|
259
|
+
// /<\s*sup[^>]*>(.*?)<\s*\/\s*sup\s*>/i.test(text) ||
|
|
260
|
+
// /<\s*sub[^>]*>(.*?)<\s*\/\s*sub\s*>/i.test(text)
|
|
261
|
+
// }
|
|
486
262
|
|
|
487
|
-
|
|
263
|
+
getMimeType(hasHtml, hasMath) {
|
|
264
|
+
let outputTex = this.props.outputTex ?? false;
|
|
265
|
+
let outputPlainText = this.props.outputPlainText ?? false;
|
|
488
266
|
|
|
489
|
-
|
|
267
|
+
if (outputTex) {
|
|
268
|
+
if (hasMath || !outputPlainText) return MIME_TYPE_TEX;
|
|
269
|
+
else return MIME_TYPE_PLAIN_TEXT;
|
|
270
|
+
}
|
|
490
271
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
this.removeNewLine();
|
|
494
|
-
e.preventDefault();
|
|
495
|
-
}
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
272
|
+
if (hasMath) return MIME_TYPE_ZZISH_HTML_MATH;
|
|
273
|
+
if (hasHtml || !outputPlainText) return MIME_TYPE_HTML;
|
|
498
274
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
// OK! We need to delete the whole Katex node and set the cursor
|
|
502
|
-
// console.log("Deleting content before: "+start_node_index+"->"+start_offset)
|
|
503
|
-
// console.log(rangeParams)
|
|
504
|
-
|
|
505
|
-
let node = prevNode;
|
|
506
|
-
let new_index = 0;
|
|
507
|
-
let new_offset = 0;
|
|
508
|
-
|
|
509
|
-
// Note that if startNodeIndex == 2, then that means that the latex node is at the start
|
|
510
|
-
// of the whole input at index 1. This is because we insert a SMALL_SPACE
|
|
511
|
-
// at the beginning in index 0, if the whole input starts with a latex node
|
|
512
|
-
if (start_node_index === 2) {
|
|
513
|
-
if (this.inputStartsWithSmallSpace()) {
|
|
514
|
-
new_index = 0;
|
|
515
|
-
new_offset = 0;
|
|
516
|
-
} else {
|
|
517
|
-
new_index = 0;
|
|
518
|
-
new_offset = getNodeTextLength(this._findFirstTextNode());
|
|
519
|
-
}
|
|
520
|
-
} else {
|
|
521
|
-
new_index = start_node_index - 2;
|
|
522
|
-
let new_node = this._findNodeWithIndex(new_index);
|
|
523
|
-
if (isKatexNode(new_node)) {
|
|
524
|
-
console.error("Two adjacent latex nodes. This should not happen!");
|
|
525
|
-
new_index = 0;
|
|
526
|
-
new_offset = 0;
|
|
527
|
-
}
|
|
528
|
-
new_node = this._findNodeWithIndex(new_index);
|
|
529
|
-
new_offset = getNodeTextLength(new_node);
|
|
530
|
-
if (new_offset < 0) new_offset = 0;
|
|
275
|
+
return MIME_TYPE_PLAIN_TEXT;
|
|
531
276
|
}
|
|
532
277
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
startOffset: new_offset,
|
|
536
|
-
endNodeIndex: new_index,
|
|
537
|
-
endOffset: new_offset,
|
|
278
|
+
_countNodeTypes = () => {
|
|
279
|
+
return countNodeTypes(this.editableDiv);
|
|
538
280
|
};
|
|
539
281
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
let rangeParams = this._getRangeParams();
|
|
584
|
-
let index = rangeParams.startNodeIndex;
|
|
585
|
-
let offset = rangeParams.startOffset;
|
|
586
|
-
|
|
587
|
-
// console.log("handleKeyDownArrowLeft")
|
|
588
|
-
// console.log(rangeParams)
|
|
589
|
-
let new_globalOffset = rangeParams.endGlobalOffset - 1;
|
|
590
|
-
let new_rangeParams = null
|
|
591
|
-
if (e.shiftKey && new_globalOffset > rangeParams.startGlobalOffset) {
|
|
592
|
-
new_rangeParams = {
|
|
593
|
-
startGlobalOffset: rangeParams.startGlobalOffset,
|
|
594
|
-
endGlobalOffset: new_globalOffset,
|
|
282
|
+
applyChangesToComponent = (value, mimeType, rangeParams, isExpertMode, selectedTab) => {
|
|
283
|
+
// rangeParams, isExpertMode and selectedTab can be null, in which case we will set them
|
|
284
|
+
if (rangeParams === null) rangeParams = this.lastSetRangeParams;
|
|
285
|
+
|
|
286
|
+
this.afterComponentUpdateData = { rangeParams, value };
|
|
287
|
+
|
|
288
|
+
let options = this.props.options || DEFAULT_OPTIONS;
|
|
289
|
+
if (isExpertMode === null) isExpertMode = options.isExpertMode;
|
|
290
|
+
if (selectedTab === null) selectedTab = options.selectedTab;
|
|
291
|
+
|
|
292
|
+
// Add this to the history
|
|
293
|
+
this.undoHistory.push({ value, mimeType, rangeParams });
|
|
294
|
+
if (this.undoHistory.length > MAX_HISTORY_LENGTH) this.undoHistory.shift();
|
|
295
|
+
|
|
296
|
+
// Call the parent handler to apply the changes
|
|
297
|
+
this.onChangeCallback({ value, mimeType }, { isExpertMode, selectedTab });
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
initialiseHistory = () => {
|
|
301
|
+
if (this.initialHistoryState == null)
|
|
302
|
+
this.initialHistoryState = {
|
|
303
|
+
value: this.props.value,
|
|
304
|
+
mimeType: this.props.mimeType,
|
|
305
|
+
rangeParams: this._getRangeParams(),
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
resetHistoryRedo = () => {
|
|
310
|
+
this.redoHistory = [];
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
deepEqual = (v1, v2) => {
|
|
314
|
+
if (v1 === v2) return true;
|
|
315
|
+
|
|
316
|
+
if (v1 == null || v2 == null || typeof v1 != "object" || typeof v2 != "object")
|
|
317
|
+
return false;
|
|
318
|
+
|
|
319
|
+
let v1keys = Object.keys(v1);
|
|
320
|
+
let v2keys = Object.keys(v2);
|
|
321
|
+
if (v1keys.length !== v2keys.length) return false;
|
|
322
|
+
|
|
323
|
+
for (let key of v1keys) {
|
|
324
|
+
if (!v2keys.includes(key) || !this.deepEqual(v1[key], v2[key])) return false;
|
|
595
325
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
326
|
+
return true;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
undo = () => {
|
|
330
|
+
const currentState = this.undoHistory.pop();
|
|
331
|
+
|
|
332
|
+
let previousState = this.undoHistory.pop();
|
|
333
|
+
if (previousState === null || previousState === undefined) {
|
|
334
|
+
previousState = this.initialHistoryState;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (this.redoHistory.length > 0) {
|
|
338
|
+
let redoLast = this.redoHistory[this.redoHistory.length - 1];
|
|
339
|
+
if (!this.deepEqual(redoLast, currentState)) this.redoHistory.push(currentState);
|
|
340
|
+
} else this.redoHistory.push(currentState);
|
|
341
|
+
|
|
342
|
+
this.applyChangesToComponent(
|
|
343
|
+
previousState.value,
|
|
344
|
+
previousState.mimeType,
|
|
345
|
+
previousState.rangeParams
|
|
346
|
+
);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
redo = () => {
|
|
350
|
+
const redoState = this.redoHistory.pop();
|
|
351
|
+
if ((redoState !== null) & (redoState !== undefined)) {
|
|
352
|
+
this.applyChangesToComponent(
|
|
353
|
+
redoState.value,
|
|
354
|
+
redoState.mimeType,
|
|
355
|
+
redoState.rangeParams
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
setOldRangeParams(params) {
|
|
361
|
+
this.oldRangeParams = params;
|
|
626
362
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
editableDivIsPlainText = () => {
|
|
633
|
-
return (
|
|
634
|
-
this.editableDiv.childNodes === null ||
|
|
635
|
-
this.editableDiv.childNodes.length === 0
|
|
636
|
-
);
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
handleKeyDownDollar = (e) => {
|
|
640
|
-
// This method automatically converts text between a pair of typed dollars into
|
|
641
|
-
// a latex math component. It only does this when the second dollar is preceded by certain
|
|
642
|
-
// characters and NOT when the second dollar is preceded by a space.
|
|
643
|
-
//
|
|
644
|
-
// This is because when the user actually wants actual dollar symbols for ammounts
|
|
645
|
-
// the dollar symbol will usually be preceded by a space, eg:
|
|
646
|
-
//
|
|
647
|
-
// "The pie was $3.50 and the cheese was $1.30"
|
|
648
|
-
//
|
|
649
|
-
// will not be converted as the $1.30 is preceded by a space.
|
|
650
|
-
//
|
|
651
|
-
try {
|
|
652
|
-
let params = this._getRangeParams();
|
|
653
|
-
let node = this._findNodeWithIndex(params.startNodeIndex);
|
|
654
|
-
if (node.nodeType !== 3)
|
|
655
|
-
// text node
|
|
656
|
-
return;
|
|
657
|
-
let text = node.nodeValue;
|
|
658
|
-
// Look for starting dollar to match this new second dollar
|
|
659
|
-
let first_dollar_pos = text.indexOf("$");
|
|
660
|
-
if (first_dollar_pos < 0) return;
|
|
661
|
-
if (first_dollar_pos >= params.startOffset) return;
|
|
662
|
-
let second_dollar_pos = params.startOffset;
|
|
663
|
-
|
|
664
|
-
let c = text.charAt(second_dollar_pos - 1);
|
|
665
|
-
let convert_to_latex = false;
|
|
666
|
-
if (
|
|
667
|
-
(c >= "a" && c <= "z") ||
|
|
668
|
-
(c >= "A" && c <= "Z") ||
|
|
669
|
-
(c >= "0" && c <= "9") ||
|
|
670
|
-
c === ")" ||
|
|
671
|
-
c === "}"
|
|
672
|
-
) {
|
|
673
|
-
convert_to_latex = true;
|
|
674
|
-
}
|
|
675
|
-
if (!convert_to_latex) return;
|
|
676
|
-
|
|
677
|
-
// So we will convert to latex
|
|
678
|
-
// If we get the raw text without replacing the dollars with marks, the dollars
|
|
679
|
-
// will be converted to html codes. So we will replace them with marks and then
|
|
680
|
-
// replace the marks with dollars later
|
|
681
|
-
// Remove first dollar and insert marks into the node instead of dollars
|
|
682
|
-
let latex = node.nodeValue.substring(
|
|
683
|
-
first_dollar_pos + 1,
|
|
684
|
-
second_dollar_pos
|
|
685
|
-
);
|
|
686
|
-
|
|
687
|
-
node.nodeValue =
|
|
688
|
-
node.nodeValue.substring(0, first_dollar_pos) +
|
|
689
|
-
MARK +
|
|
690
|
-
latex +
|
|
691
|
-
MARK +
|
|
692
|
-
node.nodeValue.substring(second_dollar_pos);
|
|
693
|
-
|
|
694
|
-
// Get the raw text and now put the dollars back
|
|
695
|
-
let raw_text = elementToMarkedRawText(
|
|
696
|
-
e.target,
|
|
697
|
-
null,
|
|
698
|
-
0,
|
|
699
|
-
this.enableHtml()
|
|
700
|
-
);
|
|
701
|
-
// now replace the marks with math
|
|
702
|
-
raw_text = replaceMarksWithMath(raw_text, latex, this.enableHtml());
|
|
703
|
-
|
|
704
|
-
// We are turning one node into three and want the cursor at the start of the third node
|
|
705
|
-
let new_rangeParams = {
|
|
706
|
-
startNodeIndex: params.startNodeIndex + 2,
|
|
707
|
-
startOffset: SMALL_SPACE_LENGTH,
|
|
708
|
-
endNodeIndex: params.startNodeIndex + 2,
|
|
709
|
-
endOffset: SMALL_SPACE_LENGTH,
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
let nodeCounts = this._countNodeTypes();
|
|
713
|
-
let mimeType = this.getMimeType(nodeCounts.html > 0, true);
|
|
714
|
-
raw_text = removeEncodingIfPlainText(
|
|
715
|
-
raw_text,
|
|
716
|
-
mimeType,
|
|
717
|
-
this.enableHtml()
|
|
718
|
-
);
|
|
719
|
-
|
|
720
|
-
this.applyChangesToComponent(
|
|
721
|
-
raw_text,
|
|
722
|
-
mimeType,
|
|
723
|
-
new_rangeParams,
|
|
724
|
-
this.props.useExpertMode,
|
|
725
|
-
this.props.selectedTab
|
|
726
|
-
);
|
|
727
|
-
} catch (error) {
|
|
728
|
-
console.error(error);
|
|
363
|
+
|
|
364
|
+
getOldRangeParams() {
|
|
365
|
+
if (this.oldRangeParams === null || this.oldRangeParams === undefined) return null;
|
|
366
|
+
let params = { ...this.oldRangeParams };
|
|
367
|
+
return params;
|
|
729
368
|
}
|
|
730
369
|
|
|
731
|
-
|
|
732
|
-
|
|
370
|
+
_getRangeParams(editableDiv = null) {
|
|
371
|
+
if (editableDiv === null) editableDiv = this.editableDiv;
|
|
372
|
+
let rangeParams = getRangeParams(editableDiv);
|
|
373
|
+
return rangeParams;
|
|
374
|
+
}
|
|
733
375
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
376
|
+
_setRangeParams = (params) => {
|
|
377
|
+
try {
|
|
378
|
+
setRangeParams(this.editableDiv, params);
|
|
379
|
+
this.setOldRangeParams(params);
|
|
380
|
+
this.lastSetRangeParmas = params;
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.error(error);
|
|
383
|
+
console.log(this.editableDiv.childNodes);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
738
386
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
if (this.enableMultiline()) this.insertNewLine(e);
|
|
742
|
-
} catch (error) {
|
|
743
|
-
console.error(error);
|
|
744
|
-
}
|
|
745
|
-
e.preventDefault();
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
handleKeyDownAccentHelper = (e) => {
|
|
749
|
-
// console.log(e.ctrlKey)
|
|
750
|
-
// console.log(e.altKey)
|
|
751
|
-
// console.log(e.shiftKey)
|
|
752
|
-
let new_accent = "";
|
|
753
|
-
let new_char = "";
|
|
754
|
-
// console.log(e.key)
|
|
755
|
-
if (e.ctrlKey) {
|
|
756
|
-
// First check for CTRL+ALT+SHIFT keys
|
|
757
|
-
if (e.shiftKey && e.altKey) {
|
|
758
|
-
console.log(e.key);
|
|
759
|
-
if (CTRL_ALT_SHIFT_LETTERS.hasOwnProperty(e.key))
|
|
760
|
-
new_char = CTRL_ALT_SHIFT_LETTERS[e.key];
|
|
761
|
-
}
|
|
762
|
-
// Second check special case of CTRL-tilde
|
|
763
|
-
else if (e.shiftKey && e.key === "`") new_accent = "~";
|
|
764
|
-
// Finally check for normal accent keys
|
|
765
|
-
else if (LETTERS.hasOwnProperty(e.key)) new_accent = e.key;
|
|
766
|
-
} else if (this.accent !== "") {
|
|
767
|
-
// Ignore key events for shift and caps lock as that can mean the user
|
|
768
|
-
// is simply changing to or from capitals and we don't want to reset
|
|
769
|
-
// this.accent in this case
|
|
770
|
-
if (e.key === "Shift") return false;
|
|
771
|
-
if (e.key === "CapsLock") return false;
|
|
772
|
-
// Get the possible letters that use this accent and test to see if the
|
|
773
|
-
// current key pressed is one of those letters.
|
|
774
|
-
let letters = LETTERS[this.accent];
|
|
775
|
-
if (letters.hasOwnProperty(e.key)) new_char = letters[e.key];
|
|
387
|
+
_findNodeWithIndex(index) {
|
|
388
|
+
return findNodeWithIndex(this.editableDiv, index);
|
|
776
389
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
e.preventDefault();
|
|
781
|
-
this.accent = new_accent;
|
|
782
|
-
// console.log("Accent set: " + this.accent);
|
|
783
|
-
return true;
|
|
390
|
+
|
|
391
|
+
_findIndexOfNode(node) {
|
|
392
|
+
return findIndexOfNode(this.editableDiv, node);
|
|
784
393
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
// Insert the accented character a the cursor position
|
|
789
|
-
this.insertRawText(new_char, false);
|
|
790
|
-
return true;
|
|
394
|
+
|
|
395
|
+
_findFirstTextNode() {
|
|
396
|
+
return findFirstTextNode(this.editableDiv);
|
|
791
397
|
}
|
|
792
|
-
// Mechanism 2 : Automatically convert a/` into à, for example
|
|
793
|
-
// if (ACCENT_KEYS.includes(e.key)) {
|
|
794
|
-
// let pos = e.target.selectionStart
|
|
795
|
-
// let text = e.target.value
|
|
796
|
-
// if (pos < 2)
|
|
797
|
-
// return
|
|
798
|
-
// if (text.charAt(pos-1) !== '/')
|
|
799
|
-
// return
|
|
800
|
-
// let letters = LETTERS[e.key]
|
|
801
|
-
// let old_letter = text.charAt(pos-2)
|
|
802
|
-
// if (letters.hasOwnProperty(old_letter)) {
|
|
803
|
-
// e.preventDefault()
|
|
804
|
-
// text = text.substring(0,pos-2) + letters[old_letter] + text.substring(pos)
|
|
805
|
-
// this.setState({value:text},
|
|
806
|
-
// () => {e.target.selectionStart = e.target.selectionEnd = pos - 1
|
|
807
|
-
// })
|
|
808
|
-
// return true
|
|
809
|
-
// }
|
|
810
|
-
// }
|
|
811
|
-
// if (CTRL_ALT_SHIFT_KEYS.includes(e.key)) {
|
|
812
|
-
// let pos = e.target.selectionStart
|
|
813
|
-
// let text = e.target.value
|
|
814
|
-
// if (pos < 1)
|
|
815
|
-
// return
|
|
816
|
-
// if (text.charAt(pos-1) !== '/')
|
|
817
|
-
// return
|
|
818
|
-
// e.preventDefault()
|
|
819
|
-
// text = text.substring(0,pos-1) + CTRL_ALT_SHIFT_LETTERS[e.key] + text.substring(pos)
|
|
820
|
-
// this.setState({value:text},
|
|
821
|
-
// () => {e.target.selectionStart = e.target.selectionEnd = pos
|
|
822
|
-
// })
|
|
823
|
-
// return true
|
|
824
|
-
// }
|
|
825
|
-
};
|
|
826
398
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
399
|
+
handleKeyDownBackspace = (e) => {
|
|
400
|
+
let rangeParams = this._getRangeParams();
|
|
401
|
+
let start_node_index = rangeParams.startNodeIndex;
|
|
402
|
+
let end_node_index = rangeParams.endNodeIndex;
|
|
403
|
+
let start_offset = rangeParams.startOffset;
|
|
404
|
+
let end_offset = rangeParams.endOffset;
|
|
831
405
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
e.key === "-" ||
|
|
849
|
-
e.key === "_" ||
|
|
850
|
-
e.key === "+" ||
|
|
851
|
-
e.key === "=") &&
|
|
852
|
-
this.enableHtml()
|
|
853
|
-
) {
|
|
854
|
-
if (e.key === "b") this.styleText(e, "Bold");
|
|
855
|
-
if (e.key === "i") this.styleText(e, "Italic");
|
|
856
|
-
if (e.key === "u") this.styleText(e, "Underline");
|
|
857
|
-
|
|
858
|
-
// Don't apply superscript is ctrl ^, this is reserved for creating accents
|
|
406
|
+
if (start_node_index < 0)
|
|
407
|
+
// Empty input field
|
|
408
|
+
return;
|
|
409
|
+
|
|
410
|
+
let startNode = this._findNodeWithIndex(start_node_index);
|
|
411
|
+
let endNode = this._findNodeWithIndex(end_node_index);
|
|
412
|
+
|
|
413
|
+
// Prevent backspace if start or end of selected region is in a katex node
|
|
414
|
+
if (isKatexNode(startNode) || isKatexNode(endNode)) {
|
|
415
|
+
console.error("Backspace inside latex node. This shouln't happen.");
|
|
416
|
+
e.preventDefault();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Handle special case of deleting last character when in bold etc text
|
|
420
|
+
// in order to resolve a bug
|
|
421
|
+
let deleteToEmpty = false;
|
|
859
422
|
if (
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
423
|
+
start_node_index === 0 &&
|
|
424
|
+
start_offset === 1 &&
|
|
425
|
+
end_node_index === 0 &&
|
|
426
|
+
end_offset === 1
|
|
863
427
|
) {
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
// if (/(Mac)/i.test(window.navigator.platform) && e.metaKey && e.key === "-")
|
|
873
|
-
// return
|
|
874
|
-
// otherwise
|
|
875
|
-
if (e.key === "-" || e.key === "_") this.styleText(e, "Subscript");
|
|
876
|
-
|
|
877
|
-
e.preventDefault();
|
|
878
|
-
return;
|
|
428
|
+
if (!isKatexNode(startNode) && getNodeTextLength(startNode) === 1) deleteToEmpty = true;
|
|
429
|
+
}
|
|
430
|
+
if (start_node_index === 0 && start_offset === 0 && end_node_index !== 0) {
|
|
431
|
+
if (
|
|
432
|
+
getNodeTextLength(endNode) === end_offset &&
|
|
433
|
+
this._findNodeWithIndex(end_node_index + 1) === null
|
|
434
|
+
)
|
|
435
|
+
deleteToEmpty = true;
|
|
879
436
|
}
|
|
880
|
-
}
|
|
881
437
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
// long key press
|
|
900
|
-
if (/(Win)/i.test(window.navigator.platform)) {
|
|
901
|
-
if (e.key === this.keyPressed && this.keyPressStartTime > 0) {
|
|
902
|
-
if (
|
|
903
|
-
e.key.length === 1 &&
|
|
904
|
-
(("a" <= e.key && e.key <= "z") ||
|
|
905
|
-
("A" <= e.key && e.key <= "Z") ||
|
|
906
|
-
e.key === "!" ||
|
|
907
|
-
e.key === "?")
|
|
908
|
-
) {
|
|
438
|
+
if (deleteToEmpty) {
|
|
439
|
+
// console.log("deleteToEmpty")
|
|
440
|
+
let new_rangeParams = {
|
|
441
|
+
startNodeIndex: 0,
|
|
442
|
+
startOffset: 1,
|
|
443
|
+
endNodeIndex: 0,
|
|
444
|
+
endOffset: 1,
|
|
445
|
+
startGlobalOffset: 0,
|
|
446
|
+
endGlobalOffset: 0,
|
|
447
|
+
};
|
|
448
|
+
this.applyChangesToComponent(
|
|
449
|
+
"",
|
|
450
|
+
this.getMimeType(false, false),
|
|
451
|
+
new_rangeParams,
|
|
452
|
+
this.props.useExpertMode,
|
|
453
|
+
this.props.selectedTab
|
|
454
|
+
);
|
|
909
455
|
e.preventDefault();
|
|
910
456
|
return;
|
|
911
|
-
}
|
|
912
457
|
}
|
|
913
|
-
}
|
|
914
458
|
|
|
915
|
-
|
|
916
|
-
|
|
459
|
+
// Treat backspace normally in the following situations
|
|
460
|
+
if (start_offset > 1) return;
|
|
461
|
+
if (start_node_index <= 0) return;
|
|
462
|
+
if (start_node_index !== rangeParams.endNodeIndex || start_offset !== rangeParams.endOffset)
|
|
463
|
+
return;
|
|
464
|
+
|
|
465
|
+
// At this point we must have start_offset === 0 or 1 and a previous node
|
|
466
|
+
|
|
467
|
+
let prevNode = this._findNodeWithIndex(start_node_index - 1);
|
|
468
|
+
|
|
469
|
+
if (!isKatexNode(prevNode)) {
|
|
470
|
+
if (this.nodeStartsWithSmallSpace(startNode)) {
|
|
471
|
+
this.removeNewLine();
|
|
472
|
+
e.preventDefault();
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// The previous node is a katex node
|
|
478
|
+
|
|
479
|
+
// OK! We need to delete the whole Katex node and set the cursor
|
|
480
|
+
// console.log("Deleting content before: "+start_node_index+"->"+start_offset)
|
|
481
|
+
// console.log(rangeParams)
|
|
482
|
+
|
|
483
|
+
let node = prevNode;
|
|
484
|
+
let new_index = 0;
|
|
485
|
+
let new_offset = 0;
|
|
486
|
+
|
|
487
|
+
// Note that if startNodeIndex == 2, then that means that the latex node is at the start
|
|
488
|
+
// of the whole input at index 1. This is because we insert a SMALL_SPACE
|
|
489
|
+
// at the beginning in index 0, if the whole input starts with a latex node
|
|
490
|
+
if (start_node_index === 2) {
|
|
491
|
+
if (this.inputStartsWithSmallSpace()) {
|
|
492
|
+
new_index = 0;
|
|
493
|
+
new_offset = 0;
|
|
494
|
+
} else {
|
|
495
|
+
new_index = 0;
|
|
496
|
+
new_offset = getNodeTextLength(this._findFirstTextNode());
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
new_index = start_node_index - 2;
|
|
500
|
+
let new_node = this._findNodeWithIndex(new_index);
|
|
501
|
+
if (isKatexNode(new_node)) {
|
|
502
|
+
console.error("Two adjacent latex nodes. This should not happen!");
|
|
503
|
+
new_index = 0;
|
|
504
|
+
new_offset = 0;
|
|
505
|
+
}
|
|
506
|
+
new_node = this._findNodeWithIndex(new_index);
|
|
507
|
+
new_offset = getNodeTextLength(new_node);
|
|
508
|
+
if (new_offset < 0) new_offset = 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
let new_rangeParams = {
|
|
512
|
+
startNodeIndex: new_index,
|
|
513
|
+
startOffset: new_offset,
|
|
514
|
+
endNodeIndex: new_index,
|
|
515
|
+
endOffset: new_offset,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// OK, time to delete the katex node!
|
|
519
|
+
node.parentNode.removeChild(node);
|
|
520
|
+
let raw_text = elementToMarkedRawText(this.editableDiv, null, 0, this.enableHtml());
|
|
521
|
+
// console.log("After deleting katex: "+raw_text)
|
|
522
|
+
|
|
523
|
+
let nodeCounts = this._countNodeTypes();
|
|
524
|
+
let mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.math > 0);
|
|
525
|
+
raw_text = removeEncodingIfPlainText(raw_text, mimeType, this.enableHtml());
|
|
526
|
+
|
|
527
|
+
this.applyChangesToComponent(
|
|
528
|
+
raw_text,
|
|
529
|
+
mimeType,
|
|
530
|
+
new_rangeParams,
|
|
531
|
+
this.props.useExpertMode,
|
|
532
|
+
this.props.selectedTab
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
// Consume the event
|
|
536
|
+
e.preventDefault();
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
nodeStartsWithSmallSpace(node) {
|
|
917
540
|
if (
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
)
|
|
930
|
-
|
|
931
|
-
|
|
541
|
+
node.nodeValue !== null &&
|
|
542
|
+
node.nodeValue !== undefined &&
|
|
543
|
+
node.nodeValue.length > 0 &&
|
|
544
|
+
node.nodeValue.charAt(0) === SMALL_SPACE
|
|
545
|
+
)
|
|
546
|
+
return true;
|
|
547
|
+
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
inputStartsWithSmallSpace = () => {
|
|
552
|
+
return startsWithSmallSpace(this.editableDiv);
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
handleKeyDownArrowLeft = (e) => {
|
|
556
|
+
let rangeParams = this._getRangeParams();
|
|
557
|
+
let index = rangeParams.startNodeIndex;
|
|
558
|
+
let offset = rangeParams.startOffset;
|
|
559
|
+
|
|
560
|
+
// console.log("handleKeyDownArrowLeft")
|
|
561
|
+
// console.log(rangeParams)
|
|
562
|
+
let new_globalOffset = rangeParams.endGlobalOffset - 1;
|
|
563
|
+
let new_rangeParams = null;
|
|
564
|
+
if (e.shiftKey && new_globalOffset > rangeParams.startGlobalOffset) {
|
|
565
|
+
new_rangeParams = {
|
|
566
|
+
startGlobalOffset: rangeParams.startGlobalOffset,
|
|
567
|
+
endGlobalOffset: new_globalOffset,
|
|
568
|
+
};
|
|
932
569
|
} else {
|
|
933
|
-
|
|
934
|
-
|
|
570
|
+
new_rangeParams = {
|
|
571
|
+
startGlobalOffset: new_globalOffset,
|
|
572
|
+
endGlobalOffset: new_globalOffset,
|
|
573
|
+
};
|
|
935
574
|
}
|
|
936
|
-
|
|
575
|
+
// console.log(new_rangeParams)
|
|
576
|
+
e.preventDefault();
|
|
577
|
+
this._setRangeParams(new_rangeParams);
|
|
937
578
|
return;
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
this.keyPressed = e.key; // store for use in methods
|
|
941
|
-
this.setState({ keyPressed: e.key });
|
|
942
|
-
|
|
943
|
-
// Hide the accent bar if a key is typed except when it is an automatic key repeat press
|
|
944
|
-
// If it is automatic key repeat, then keyPressStartTime will be greater than 0
|
|
945
|
-
// (keyPressStartTime is reset to -1 on key up)
|
|
946
|
-
if (this.keyPressStartTime < 0) {
|
|
947
|
-
this.keyPressStartTime = Date.now();
|
|
948
|
-
if (this.state.showAccentBar) {
|
|
949
|
-
console.log("Hiding accent bar 2")
|
|
950
|
-
this.setState({ showAccentBar: false });
|
|
951
|
-
}
|
|
952
|
-
}
|
|
579
|
+
};
|
|
953
580
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
581
|
+
handleKeyDownArrowRight = (e) => {
|
|
582
|
+
// console.log("handleKeyDownArrowRight")
|
|
583
|
+
let rangeParams = this._getRangeParams();
|
|
584
|
+
let index = rangeParams.startNodeIndex;
|
|
585
|
+
let offset = rangeParams.startOffset;
|
|
586
|
+
|
|
587
|
+
let new_rangeParams = null;
|
|
588
|
+
let new_globalOffset = rangeParams.endGlobalOffset + 1;
|
|
589
|
+
if (e.shiftKey && new_globalOffset > rangeParams.startGlobalOffset) {
|
|
590
|
+
new_rangeParams = {
|
|
591
|
+
startGlobalOffset: rangeParams.startGlobalOffset,
|
|
592
|
+
endGlobalOffset: new_globalOffset,
|
|
593
|
+
};
|
|
594
|
+
} else {
|
|
595
|
+
new_rangeParams = {
|
|
596
|
+
startGlobalOffset: new_globalOffset,
|
|
597
|
+
endGlobalOffset: new_globalOffset,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
e.preventDefault();
|
|
601
|
+
this._setRangeParams(new_rangeParams);
|
|
968
602
|
return;
|
|
969
|
-
|
|
603
|
+
};
|
|
970
604
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
605
|
+
editableDivIsPlainText = () => {
|
|
606
|
+
return this.editableDiv.childNodes === null || this.editableDiv.childNodes.length === 0;
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
handleKeyDownDollar = (e) => {
|
|
610
|
+
// This method automatically converts text between a pair of typed dollars into
|
|
611
|
+
// a latex math component. It only does this when the second dollar is preceded by certain
|
|
612
|
+
// characters and NOT when the second dollar is preceded by a space.
|
|
613
|
+
//
|
|
614
|
+
// This is because when the user actually wants actual dollar symbols for ammounts
|
|
615
|
+
// the dollar symbol will usually be preceded by a space, eg:
|
|
616
|
+
//
|
|
617
|
+
// "The pie was $3.50 and the cheese was $1.30"
|
|
618
|
+
//
|
|
619
|
+
// will not be converted as the $1.30 is preceded by a space.
|
|
620
|
+
//
|
|
621
|
+
try {
|
|
622
|
+
let params = this._getRangeParams();
|
|
623
|
+
let node = this._findNodeWithIndex(params.startNodeIndex);
|
|
624
|
+
if (node.nodeType !== 3)
|
|
625
|
+
// text node
|
|
626
|
+
return;
|
|
627
|
+
let text = node.nodeValue;
|
|
628
|
+
// Look for starting dollar to match this new second dollar
|
|
629
|
+
let first_dollar_pos = text.indexOf("$");
|
|
630
|
+
if (first_dollar_pos < 0) return;
|
|
631
|
+
if (first_dollar_pos >= params.startOffset) return;
|
|
632
|
+
let second_dollar_pos = params.startOffset;
|
|
633
|
+
|
|
634
|
+
let c = text.charAt(second_dollar_pos - 1);
|
|
635
|
+
let convert_to_latex = false;
|
|
636
|
+
if (
|
|
637
|
+
(c >= "a" && c <= "z") ||
|
|
638
|
+
(c >= "A" && c <= "Z") ||
|
|
639
|
+
(c >= "0" && c <= "9") ||
|
|
640
|
+
c === ")" ||
|
|
641
|
+
c === "}"
|
|
642
|
+
) {
|
|
643
|
+
convert_to_latex = true;
|
|
644
|
+
}
|
|
645
|
+
if (!convert_to_latex) return;
|
|
646
|
+
|
|
647
|
+
// So we will convert to latex
|
|
648
|
+
// If we get the raw text without replacing the dollars with marks, the dollars
|
|
649
|
+
// will be converted to html codes. So we will replace them with marks and then
|
|
650
|
+
// replace the marks with dollars later
|
|
651
|
+
// Remove first dollar and insert marks into the node instead of dollars
|
|
652
|
+
let latex = node.nodeValue.substring(first_dollar_pos + 1, second_dollar_pos);
|
|
653
|
+
|
|
654
|
+
node.nodeValue =
|
|
655
|
+
node.nodeValue.substring(0, first_dollar_pos) +
|
|
656
|
+
MARK +
|
|
657
|
+
latex +
|
|
658
|
+
MARK +
|
|
659
|
+
node.nodeValue.substring(second_dollar_pos);
|
|
660
|
+
|
|
661
|
+
// Get the raw text and now put the dollars back
|
|
662
|
+
let raw_text = elementToMarkedRawText(e.target, null, 0, this.enableHtml());
|
|
663
|
+
// now replace the marks with math
|
|
664
|
+
raw_text = replaceMarksWithMath(raw_text, latex, this.enableHtml());
|
|
665
|
+
|
|
666
|
+
// We are turning one node into three and want the cursor at the start of the third node
|
|
667
|
+
let new_rangeParams = {
|
|
668
|
+
startNodeIndex: params.startNodeIndex + 2,
|
|
669
|
+
startOffset: SMALL_SPACE_LENGTH,
|
|
670
|
+
endNodeIndex: params.startNodeIndex + 2,
|
|
671
|
+
endOffset: SMALL_SPACE_LENGTH,
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
let nodeCounts = this._countNodeTypes();
|
|
675
|
+
let mimeType = this.getMimeType(nodeCounts.html > 0, true);
|
|
676
|
+
raw_text = removeEncodingIfPlainText(raw_text, mimeType, this.enableHtml());
|
|
677
|
+
|
|
678
|
+
this.applyChangesToComponent(
|
|
679
|
+
raw_text,
|
|
680
|
+
mimeType,
|
|
681
|
+
new_rangeParams,
|
|
682
|
+
this.props.useExpertMode,
|
|
683
|
+
this.props.selectedTab
|
|
684
|
+
);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
console.error(error);
|
|
986
687
|
}
|
|
987
|
-
}
|
|
988
688
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
this.handleKeyDownEnter(e);
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
689
|
+
e.preventDefault();
|
|
690
|
+
};
|
|
994
691
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
key === "l" ||
|
|
1006
|
-
key === "n" ||
|
|
1007
|
-
key === "s" ||
|
|
1008
|
-
key === "?" ||
|
|
1009
|
-
key === "!"
|
|
1010
|
-
) {
|
|
1011
|
-
let _this = this;
|
|
1012
|
-
setTimeout(function () {
|
|
1013
|
-
if (_this.keyPressStartTime > 0)
|
|
1014
|
-
if (Date.now() - _this.keyPressStartTime > 450)
|
|
1015
|
-
_this.setState({ showAccentBar: true });
|
|
1016
|
-
}, 500);
|
|
692
|
+
showEquationEditorOnKeyDown = (e) => {
|
|
693
|
+
this.showEquationEditor(true);
|
|
694
|
+
e.preventDefault();
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
handleKeyDownEnter = (e) => {
|
|
698
|
+
try {
|
|
699
|
+
if (this.enableMultiline()) this.insertNewLine(e);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
console.error(error);
|
|
1017
702
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
703
|
+
e.preventDefault();
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
handleKeyDownAccentHelper = (e) => {
|
|
707
|
+
// console.log(e.ctrlKey)
|
|
708
|
+
// console.log(e.altKey)
|
|
709
|
+
// console.log(e.shiftKey)
|
|
710
|
+
let new_accent = "";
|
|
711
|
+
let new_char = "";
|
|
712
|
+
// console.log(e.key)
|
|
713
|
+
if (e.ctrlKey) {
|
|
714
|
+
// First check for CTRL+ALT+SHIFT keys
|
|
715
|
+
if (e.shiftKey && e.altKey) {
|
|
716
|
+
console.log(e.key);
|
|
717
|
+
if (CTRL_ALT_SHIFT_LETTERS.hasOwnProperty(e.key))
|
|
718
|
+
new_char = CTRL_ALT_SHIFT_LETTERS[e.key];
|
|
719
|
+
}
|
|
720
|
+
// Second check special case of CTRL-tilde
|
|
721
|
+
else if (e.shiftKey && e.key === "`") new_accent = "~";
|
|
722
|
+
// Finally check for normal accent keys
|
|
723
|
+
else if (LETTERS.hasOwnProperty(e.key)) new_accent = e.key;
|
|
724
|
+
} else if (this.accent !== "") {
|
|
725
|
+
// Ignore key events for shift and caps lock as that can mean the user
|
|
726
|
+
// is simply changing to or from capitals and we don't want to reset
|
|
727
|
+
// this.accent in this case
|
|
728
|
+
if (e.key === "Shift") return false;
|
|
729
|
+
if (e.key === "CapsLock") return false;
|
|
730
|
+
// Get the possible letters that use this accent and test to see if the
|
|
731
|
+
// current key pressed is one of those letters.
|
|
732
|
+
let letters = LETTERS[this.accent];
|
|
733
|
+
if (letters.hasOwnProperty(e.key)) new_char = letters[e.key];
|
|
1028
734
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
735
|
+
// If a new accent has been set, store it in this.accent, otherwise clear this.accent
|
|
736
|
+
if (new_accent === "") this.accent = "";
|
|
737
|
+
else {
|
|
738
|
+
e.preventDefault();
|
|
739
|
+
this.accent = new_accent;
|
|
740
|
+
// console.log("Accent set: " + this.accent);
|
|
741
|
+
return true;
|
|
1033
742
|
}
|
|
1034
|
-
|
|
743
|
+
if (new_char !== "") {
|
|
744
|
+
// Prevent the original character being insereted
|
|
745
|
+
e.preventDefault();
|
|
746
|
+
// Insert the accented character a the cursor position
|
|
747
|
+
this.insertRawText(new_char, false);
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
// Mechanism 2 : Automatically convert a/` into à, for example
|
|
751
|
+
// if (ACCENT_KEYS.includes(e.key)) {
|
|
752
|
+
// let pos = e.target.selectionStart
|
|
753
|
+
// let text = e.target.value
|
|
754
|
+
// if (pos < 2)
|
|
755
|
+
// return
|
|
756
|
+
// if (text.charAt(pos-1) !== '/')
|
|
757
|
+
// return
|
|
758
|
+
// let letters = LETTERS[e.key]
|
|
759
|
+
// let old_letter = text.charAt(pos-2)
|
|
760
|
+
// if (letters.hasOwnProperty(old_letter)) {
|
|
761
|
+
// e.preventDefault()
|
|
762
|
+
// text = text.substring(0,pos-2) + letters[old_letter] + text.substring(pos)
|
|
763
|
+
// this.setState({value:text},
|
|
764
|
+
// () => {e.target.selectionStart = e.target.selectionEnd = pos - 1
|
|
765
|
+
// })
|
|
766
|
+
// return true
|
|
767
|
+
// }
|
|
768
|
+
// }
|
|
769
|
+
// if (CTRL_ALT_SHIFT_KEYS.includes(e.key)) {
|
|
770
|
+
// let pos = e.target.selectionStart
|
|
771
|
+
// let text = e.target.value
|
|
772
|
+
// if (pos < 1)
|
|
773
|
+
// return
|
|
774
|
+
// if (text.charAt(pos-1) !== '/')
|
|
775
|
+
// return
|
|
776
|
+
// e.preventDefault()
|
|
777
|
+
// text = text.substring(0,pos-1) + CTRL_ALT_SHIFT_LETTERS[e.key] + text.substring(pos)
|
|
778
|
+
// this.setState({value:text},
|
|
779
|
+
// () => {e.target.selectionStart = e.target.selectionEnd = pos
|
|
780
|
+
// })
|
|
781
|
+
// return true
|
|
782
|
+
// }
|
|
783
|
+
};
|
|
1035
784
|
|
|
1036
|
-
|
|
1037
|
-
|
|
785
|
+
handleKeyDown = (e) => {
|
|
786
|
+
try {
|
|
787
|
+
// Lets set up or update the undo redo history
|
|
788
|
+
this.initialiseHistory();
|
|
1038
789
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
e.preventDefault();
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
} catch (error) {
|
|
1049
|
-
console.error(error);
|
|
1050
|
-
console.log(
|
|
1051
|
-
"Key pressed: " +
|
|
1052
|
-
(e.metaKey ? "META+" : "") +
|
|
1053
|
-
(e.ctrlKey ? "CTRL+" : "") +
|
|
1054
|
-
(e.altKey ? "ALT+" : "") +
|
|
1055
|
-
(e.shiftKey ? "SHIFT+" : "") +
|
|
1056
|
-
e.key
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
|
-
};
|
|
790
|
+
if ((e.key === "z" || e.key === "Z") && (e.ctrlKey || e.metaKey)) {
|
|
791
|
+
if (e.shiftKey) this.redo();
|
|
792
|
+
else this.undo();
|
|
793
|
+
e.preventDefault();
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
1060
796
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
797
|
+
if (!(e.ctrlKey || e.metaKey)) this.resetHistoryRedo();
|
|
798
|
+
|
|
799
|
+
if (
|
|
800
|
+
(e.metaKey || e.ctrlKey) &&
|
|
801
|
+
(e.key === "b" ||
|
|
802
|
+
e.key === "i" ||
|
|
803
|
+
e.key === "u" ||
|
|
804
|
+
e.key === "6" ||
|
|
805
|
+
e.key === "^" ||
|
|
806
|
+
e.key === "-" ||
|
|
807
|
+
e.key === "_" ||
|
|
808
|
+
e.key === "+" ||
|
|
809
|
+
e.key === "=") &&
|
|
810
|
+
this.enableHtml()
|
|
811
|
+
) {
|
|
812
|
+
if (e.key === "b") this.styleText(e, "Bold");
|
|
813
|
+
if (e.key === "i") this.styleText(e, "Italic");
|
|
814
|
+
if (e.key === "u") this.styleText(e, "Underline");
|
|
815
|
+
|
|
816
|
+
// Don't apply superscript is ctrl ^, this is reserved for creating accents
|
|
817
|
+
if (
|
|
818
|
+
/(Win)/i.test(window.navigator.platform) &&
|
|
819
|
+
e.ctrlKey &&
|
|
820
|
+
(e.key === "6" || e.key === "^")
|
|
821
|
+
) {
|
|
822
|
+
// do nothing
|
|
823
|
+
} else {
|
|
824
|
+
// otherwise
|
|
825
|
+
if (e.key === "6" || e.key === "^" || e.key === "+" || e.key === "=")
|
|
826
|
+
this.styleText(e, "Superscript");
|
|
827
|
+
// Don't apply subscript if it is the browser command key for "smaller"
|
|
828
|
+
// if (/(Win)/i.test(window.navigator.platform) && e.ctrlKey && e.key === "-")
|
|
829
|
+
// return
|
|
830
|
+
// if (/(Mac)/i.test(window.navigator.platform) && e.metaKey && e.key === "-")
|
|
831
|
+
// return
|
|
832
|
+
// otherwise
|
|
833
|
+
if (e.key === "-" || e.key === "_") this.styleText(e, "Subscript");
|
|
834
|
+
|
|
835
|
+
e.preventDefault();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
1065
839
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
840
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "v") {
|
|
841
|
+
// console.log("Executing paste");
|
|
842
|
+
document.execCommand("paste");
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "c") {
|
|
846
|
+
// console.log("Executing copy");
|
|
847
|
+
document.execCommand("copy");
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "x") {
|
|
851
|
+
// console.log("Executing cut");
|
|
852
|
+
document.execCommand("cut");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
1071
855
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
856
|
+
// Ignore automatic key repeat presses on Windows so that we can show an accent bar on
|
|
857
|
+
// long key press
|
|
858
|
+
if (/(Win)/i.test(window.navigator.platform)) {
|
|
859
|
+
if (e.key === this.keyPressed && this.keyPressStartTime > 0) {
|
|
860
|
+
if (
|
|
861
|
+
e.key.length === 1 &&
|
|
862
|
+
(("a" <= e.key && e.key <= "z") ||
|
|
863
|
+
("A" <= e.key && e.key <= "Z") ||
|
|
864
|
+
e.key === "!" ||
|
|
865
|
+
e.key === "?")
|
|
866
|
+
) {
|
|
867
|
+
e.preventDefault();
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
1076
872
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
873
|
+
if (e.altKey && e.key === "Alt") {
|
|
874
|
+
let key = this.fetchAccentLetter();
|
|
875
|
+
if (
|
|
876
|
+
key === "a" ||
|
|
877
|
+
key === "e" ||
|
|
878
|
+
key === "i" ||
|
|
879
|
+
key === "o" ||
|
|
880
|
+
key === "u" ||
|
|
881
|
+
key === "c" ||
|
|
882
|
+
key === "l" ||
|
|
883
|
+
key === "n" ||
|
|
884
|
+
key === "s" ||
|
|
885
|
+
key === "?" ||
|
|
886
|
+
key === "!"
|
|
887
|
+
) {
|
|
888
|
+
console.log("showing/hiding accent bar");
|
|
889
|
+
this.setState({ showAccentBar: !this.state.showAccentBar });
|
|
890
|
+
} else {
|
|
891
|
+
console.log("hiding accent bar");
|
|
892
|
+
this.setState({ showAccentBar: false });
|
|
893
|
+
}
|
|
894
|
+
e.preventDefault();
|
|
895
|
+
return;
|
|
1090
896
|
}
|
|
1091
897
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
this.
|
|
1099
|
-
|
|
898
|
+
this.keyPressed = e.key; // store for use in methods
|
|
899
|
+
this.setState({ keyPressed: e.key });
|
|
900
|
+
|
|
901
|
+
// Hide the accent bar if a key is typed except when it is an automatic key repeat press
|
|
902
|
+
// If it is automatic key repeat, then keyPressStartTime will be greater than 0
|
|
903
|
+
// (keyPressStartTime is reset to -1 on key up)
|
|
904
|
+
if (this.keyPressStartTime < 0) {
|
|
905
|
+
this.keyPressStartTime = Date.now();
|
|
906
|
+
if (this.state.showAccentBar) {
|
|
907
|
+
console.log("Hiding accent bar 2");
|
|
908
|
+
this.setState({ showAccentBar: false });
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (e.key === "Backspace") {
|
|
913
|
+
this.handleKeyDownBackspace(e);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (e.key === "ArrowLeft") {
|
|
917
|
+
this.handleKeyDownArrowLeft(e);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (e.key === "ArrowRight") {
|
|
921
|
+
this.handleKeyDownArrowRight(e);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (e.key === "$") {
|
|
925
|
+
this.handleKeyDownDollar(e);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Implement special command + keys
|
|
930
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
931
|
+
this.showEquationEditorOnKeyDown(e);
|
|
932
|
+
return;
|
|
933
|
+
} else if (/(Mac)/i.test(window.navigator.platform)) {
|
|
934
|
+
if (e.ctrlKey && e.key === "=") {
|
|
935
|
+
// CTRL+= inserts equation in Word on Mac
|
|
936
|
+
this.showEquationEditorOnKeyDown(e);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
} else if (/(Win)/i.test(window.navigator.platform)) {
|
|
940
|
+
if (e.altKey && e.key === "=") {
|
|
941
|
+
// ALT+= inserts equation in Word on PC
|
|
942
|
+
this.showEquationEditorOnKeyDown(e);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Lets handle the enter key (new line) ourselves
|
|
948
|
+
if (e.key === "Enter") {
|
|
949
|
+
this.handleKeyDownEnter(e);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Show the accent bar on long key presses
|
|
954
|
+
if (/(Win)/i.test(window.navigator.platform)) {
|
|
955
|
+
let key = e.key.toLowerCase();
|
|
956
|
+
if (
|
|
957
|
+
key === "a" ||
|
|
958
|
+
key === "e" ||
|
|
959
|
+
key === "i" ||
|
|
960
|
+
key === "o" ||
|
|
961
|
+
key === "u" ||
|
|
962
|
+
key === "c" ||
|
|
963
|
+
key === "l" ||
|
|
964
|
+
key === "n" ||
|
|
965
|
+
key === "s" ||
|
|
966
|
+
key === "?" ||
|
|
967
|
+
key === "!"
|
|
968
|
+
) {
|
|
969
|
+
let _this = this;
|
|
970
|
+
setTimeout(function () {
|
|
971
|
+
if (_this.keyPressStartTime > 0)
|
|
972
|
+
if (Date.now() - _this.keyPressStartTime > 450)
|
|
973
|
+
_this.setState({ showAccentBar: true });
|
|
974
|
+
}, 500);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Stop editing if within rendered katex node, if somehow the cursor ends up there (it shouldn't)
|
|
979
|
+
let params = this._getRangeParams();
|
|
980
|
+
let startNode = this._findNodeWithIndex(params.startNodeIndex);
|
|
981
|
+
let endNode = this._findNodeWithIndex(params.endNodeIndex);
|
|
982
|
+
if (!this.editableDivIsPlainText()) {
|
|
983
|
+
if (params.startNodeIndex === -1) {
|
|
984
|
+
console.error("Couldn't get range params");
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (isKatexNode(startNode) || isKatexNode(endNode)) {
|
|
988
|
+
console.error("User trying to edit inside latex node");
|
|
989
|
+
e.preventDefault();
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (this.handleKeyDownAccentHelper(e)) return;
|
|
995
|
+
|
|
996
|
+
// Check if inserting character in element that starts with small space
|
|
997
|
+
if (
|
|
998
|
+
e.key.length === 1 && // ie, it's not something like "arrowLeft"
|
|
999
|
+
params.startOffset === 0 &&
|
|
1000
|
+
this.nodeStartsWithSmallSpace(startNode)
|
|
1001
|
+
) {
|
|
1002
|
+
// console.log("Handling character input at start of text node with small space")
|
|
1003
|
+
this.insertAfterSmallSpace(e.key, params);
|
|
1004
|
+
e.preventDefault();
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
console.error(error);
|
|
1009
|
+
console.log(
|
|
1010
|
+
"Key pressed: " +
|
|
1011
|
+
(e.metaKey ? "META+" : "") +
|
|
1012
|
+
(e.ctrlKey ? "CTRL+" : "") +
|
|
1013
|
+
(e.altKey ? "ALT+" : "") +
|
|
1014
|
+
(e.shiftKey ? "SHIFT+" : "") +
|
|
1015
|
+
e.key
|
|
1016
|
+
);
|
|
1100
1017
|
}
|
|
1101
|
-
|
|
1018
|
+
};
|
|
1102
1019
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1020
|
+
handleKeyUp = (e) => {
|
|
1021
|
+
try {
|
|
1022
|
+
this.keyPressed = e.key;
|
|
1023
|
+
this.keyPressStartTime = -1;
|
|
1024
|
+
|
|
1025
|
+
// If the cursor is in a katex node, then move it to the next node (unless there is no
|
|
1026
|
+
// net node in which case move it to the previous node)
|
|
1027
|
+
if (e.key === "ArrowRight") {
|
|
1028
|
+
let rangeParams = this._getRangeParams();
|
|
1029
|
+
let index = rangeParams.startNodeIndex;
|
|
1030
|
+
|
|
1031
|
+
if (index >= 0) {
|
|
1032
|
+
if (isKatexNode(this._findNodeWithIndex(index))) {
|
|
1033
|
+
let new_index = 0;
|
|
1034
|
+
let new_offset = 0;
|
|
1035
|
+
|
|
1036
|
+
let nextNode = this._findNodeWithIndex(index + 1);
|
|
1037
|
+
if (nextNode === null) {
|
|
1038
|
+
new_index = index + 1;
|
|
1039
|
+
new_offset = SMALL_SPACE_LENGTH; // Nominally should be 0, but 1 due to inserted SMALL_SPACE
|
|
1040
|
+
} else {
|
|
1041
|
+
new_index = index - 1;
|
|
1042
|
+
new_offset = getNodeTextLength(this._findNodeWithIndex(new_index));
|
|
1043
|
+
if (new_offset < 0) {
|
|
1044
|
+
console.error("Failed to get node text length");
|
|
1045
|
+
new_offset = 0;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
let new_rangeParams = {
|
|
1050
|
+
startNodeIndex: new_index,
|
|
1051
|
+
startOffset: new_offset,
|
|
1052
|
+
endNodeIndex: new_index,
|
|
1053
|
+
endOffset: new_offset,
|
|
1054
|
+
};
|
|
1055
|
+
this._setRangeParams(new_rangeParams);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1106
1059
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1060
|
+
if (e.key === "ArrowLeft") {
|
|
1061
|
+
let rangeParams = this._getRangeParams();
|
|
1062
|
+
let index = rangeParams.startNodeIndex;
|
|
1063
|
+
|
|
1064
|
+
// If the cursor is in a katex node, then move it to the previous node (unless there is no
|
|
1065
|
+
// previous node in which case move it to the next node)
|
|
1066
|
+
if (index >= 0) {
|
|
1067
|
+
if (isKatexNode(this._findNodeWithIndex(index))) {
|
|
1068
|
+
let new_index = 0;
|
|
1069
|
+
let new_offset = 0;
|
|
1070
|
+
|
|
1071
|
+
if (index - 1 >= 0) {
|
|
1072
|
+
new_index = index - 1;
|
|
1073
|
+
new_offset = getNodeTextLength(this._findNodeWithIndex(new_index));
|
|
1074
|
+
if (new_offset < 0) {
|
|
1075
|
+
console.error("Failed to get node text length");
|
|
1076
|
+
new_offset = 0;
|
|
1077
|
+
}
|
|
1078
|
+
} else {
|
|
1079
|
+
new_index = index + 1;
|
|
1080
|
+
new_offset = SMALL_SPACE_LENGTH;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
let new_rangeParams = {
|
|
1084
|
+
startNodeIndex: new_index,
|
|
1085
|
+
startOffset: new_offset,
|
|
1086
|
+
endNodeIndex: new_index,
|
|
1087
|
+
endOffset: new_offset,
|
|
1088
|
+
};
|
|
1089
|
+
// console.log(new_rangeParams)
|
|
1090
|
+
this._setRangeParams(new_rangeParams);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1126
1093
|
}
|
|
1127
1094
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
endNodeIndex: new_index,
|
|
1132
|
-
endOffset: new_offset,
|
|
1133
|
-
};
|
|
1134
|
-
// console.log(new_rangeParams)
|
|
1135
|
-
this._setRangeParams(new_rangeParams);
|
|
1136
|
-
}
|
|
1095
|
+
this.updateActiveButtons();
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
console.error(error);
|
|
1137
1098
|
}
|
|
1138
|
-
|
|
1099
|
+
};
|
|
1139
1100
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
let nodeCounts = this._countNodeTypes();
|
|
1174
|
-
let mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.math > 0);
|
|
1175
|
-
raw_text = removeEncodingIfPlainText(
|
|
1176
|
-
raw_text,
|
|
1177
|
-
mimeType,
|
|
1178
|
-
this.enableHtml()
|
|
1179
|
-
);
|
|
1180
|
-
|
|
1181
|
-
this.applyChangesToComponent(
|
|
1182
|
-
raw_text,
|
|
1183
|
-
mimeType,
|
|
1184
|
-
params,
|
|
1185
|
-
this.props.useExpertMode,
|
|
1186
|
-
this.props.selectedTab
|
|
1187
|
-
);
|
|
1188
|
-
} catch (error) {
|
|
1189
|
-
console.error(error);
|
|
1190
|
-
}
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
updateActiveButtons = () => {
|
|
1194
|
-
try {
|
|
1195
|
-
// console.log("updateActiveButtons")
|
|
1196
|
-
let rangeParams = this._getRangeParams();
|
|
1197
|
-
if (rangeParams === null || rangeParams === undefined) {
|
|
1198
|
-
console.warn(
|
|
1199
|
-
"Unable to updateActiveButtons. rangeParams: " + rangeParams
|
|
1200
|
-
);
|
|
1201
|
-
return;
|
|
1202
|
-
}
|
|
1203
|
-
if (rangeParams.startNodeIndex < 0) {
|
|
1204
|
-
console.warn(
|
|
1205
|
-
"Unable to updateActiveButtons. rangeParams.startNodeIndex: " +
|
|
1206
|
-
rangeParams.startNodeIndex
|
|
1207
|
-
);
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
1211
|
-
let bold = false;
|
|
1212
|
-
let italic = false;
|
|
1213
|
-
let underline = false;
|
|
1214
|
-
let superscript = false;
|
|
1215
|
-
let subscript = false;
|
|
1216
|
-
do {
|
|
1217
|
-
if (node.nodeType === 1) {
|
|
1218
|
-
let nodeName = node.nodeName.toLowerCase();
|
|
1219
|
-
if (nodeName === "b" || nodeName === "strong") bold = true;
|
|
1220
|
-
else if (nodeName === "i" || nodeName === "em") italic = true;
|
|
1221
|
-
else if (nodeName === "u") underline = true;
|
|
1222
|
-
else if (nodeName === "sub") subscript = true;
|
|
1223
|
-
else if (nodeName === "sup") superscript = true;
|
|
1224
|
-
} else if (
|
|
1225
|
-
node.classList !== null &&
|
|
1226
|
-
node.classList !== undefined &&
|
|
1227
|
-
node.classList.contains("MathRichInput")
|
|
1228
|
-
)
|
|
1229
|
-
break;
|
|
1230
|
-
node = node.parentNode;
|
|
1231
|
-
} while (node !== null);
|
|
1232
|
-
|
|
1233
|
-
let activeButtons = { bold, italic, underline, subscript, superscript };
|
|
1234
|
-
// console.log(activeButtons)
|
|
1235
|
-
|
|
1236
|
-
let update = true;
|
|
1237
|
-
if (this.lastSetActiveButtons !== null) {
|
|
1238
|
-
update = false;
|
|
1239
|
-
for (var style in activeButtons) {
|
|
1240
|
-
if (activeButtons[style] !== this.lastSetActiveButtons[style]) {
|
|
1241
|
-
update = true;
|
|
1242
|
-
break;
|
|
1243
|
-
}
|
|
1101
|
+
handleInput = (event) => {
|
|
1102
|
+
try {
|
|
1103
|
+
// console.log("handleInput")
|
|
1104
|
+
if (this.isComposing) return;
|
|
1105
|
+
|
|
1106
|
+
let raw_text = elementToMarkedRawText(this.editableDiv, null, 0, this.enableHtml());
|
|
1107
|
+
const params = this._getRangeParams();
|
|
1108
|
+
if (params === null || params === undefined)
|
|
1109
|
+
console.error("Could not get range params from input field");
|
|
1110
|
+
else if (params.startNodeIndex === null || params.startNodeIndex === undefined)
|
|
1111
|
+
console.error(
|
|
1112
|
+
"Could not get range params from input field, startNodeIndex===" +
|
|
1113
|
+
params.startNodeIndex
|
|
1114
|
+
);
|
|
1115
|
+
else {
|
|
1116
|
+
// console.log("Current range params:")
|
|
1117
|
+
// console.log(params)
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
let nodeCounts = this._countNodeTypes();
|
|
1121
|
+
let mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.math > 0);
|
|
1122
|
+
raw_text = removeEncodingIfPlainText(raw_text, mimeType, this.enableHtml());
|
|
1123
|
+
|
|
1124
|
+
this.applyChangesToComponent(
|
|
1125
|
+
raw_text,
|
|
1126
|
+
mimeType,
|
|
1127
|
+
params,
|
|
1128
|
+
this.props.useExpertMode,
|
|
1129
|
+
this.props.selectedTab
|
|
1130
|
+
);
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
console.error(error);
|
|
1244
1133
|
}
|
|
1245
|
-
|
|
1246
|
-
if (update) {
|
|
1247
|
-
this.lastSetActiveButtons = activeButtons;
|
|
1248
|
-
this.setState({ activeButtons: activeButtons });
|
|
1249
|
-
}
|
|
1250
|
-
} catch (error) {
|
|
1251
|
-
console.error(error);
|
|
1252
|
-
}
|
|
1253
|
-
};
|
|
1134
|
+
};
|
|
1254
1135
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1136
|
+
updateActiveButtons = () => {
|
|
1137
|
+
try {
|
|
1138
|
+
// console.log("updateActiveButtons")
|
|
1139
|
+
let rangeParams = this._getRangeParams();
|
|
1140
|
+
if (rangeParams === null || rangeParams === undefined) {
|
|
1141
|
+
console.warn("Unable to updateActiveButtons. rangeParams: " + rangeParams);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (rangeParams.startNodeIndex < 0) {
|
|
1145
|
+
console.warn(
|
|
1146
|
+
"Unable to updateActiveButtons. rangeParams.startNodeIndex: " +
|
|
1147
|
+
rangeParams.startNodeIndex
|
|
1148
|
+
);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
1152
|
+
let bold = false;
|
|
1153
|
+
let italic = false;
|
|
1154
|
+
let underline = false;
|
|
1155
|
+
let superscript = false;
|
|
1156
|
+
let subscript = false;
|
|
1157
|
+
do {
|
|
1158
|
+
if (node.nodeType === 1) {
|
|
1159
|
+
let nodeName = node.nodeName.toLowerCase();
|
|
1160
|
+
if (nodeName === "b" || nodeName === "strong") bold = true;
|
|
1161
|
+
else if (nodeName === "i" || nodeName === "em") italic = true;
|
|
1162
|
+
else if (nodeName === "u") underline = true;
|
|
1163
|
+
else if (nodeName === "sub") subscript = true;
|
|
1164
|
+
else if (nodeName === "sup") superscript = true;
|
|
1165
|
+
} else if (
|
|
1166
|
+
node.classList !== null &&
|
|
1167
|
+
node.classList !== undefined &&
|
|
1168
|
+
node.classList.contains("MathRichInput")
|
|
1169
|
+
)
|
|
1170
|
+
break;
|
|
1171
|
+
node = node.parentNode;
|
|
1172
|
+
} while (node !== null);
|
|
1173
|
+
|
|
1174
|
+
let activeButtons = { bold, italic, underline, subscript, superscript };
|
|
1175
|
+
// console.log(activeButtons)
|
|
1176
|
+
|
|
1177
|
+
let update = true;
|
|
1178
|
+
if (this.lastSetActiveButtons !== null) {
|
|
1179
|
+
update = false;
|
|
1180
|
+
for (var style in activeButtons) {
|
|
1181
|
+
if (activeButtons[style] !== this.lastSetActiveButtons[style]) {
|
|
1182
|
+
update = true;
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
if (update) {
|
|
1188
|
+
this.lastSetActiveButtons = activeButtons;
|
|
1189
|
+
this.setState({ activeButtons: activeButtons });
|
|
1190
|
+
}
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
console.error(error);
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
handlePaste = (event) => {
|
|
1197
|
+
let clipboardData = event.clipboardData || window.clipboardData;
|
|
1198
|
+
if (clipboardData === null || clipboardData === undefined) {
|
|
1199
|
+
console.warn("handlePaste called, but no clipboard data");
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
// for (let i = 0; i < clipboardData.types.length; i++)
|
|
1203
|
+
// console.log(
|
|
1204
|
+
// clipboardData.types[i] +
|
|
1205
|
+
// ": " +
|
|
1206
|
+
// clipboardData.getData(clipboardData.types[i])
|
|
1207
|
+
// );
|
|
1208
|
+
let pasteHtml = clipboardData.getData("text/html");
|
|
1209
|
+
if (pasteHtml !== null && pasteHtml.length > 0) {
|
|
1210
|
+
// console.log("** Paste html");
|
|
1211
|
+
// console.log(pasteHtml);
|
|
1212
|
+
pasteHtml = sanitiseHtml(pasteHtml);
|
|
1213
|
+
// console.log("** Santised paste html");
|
|
1214
|
+
// console.log(pasteHtml);
|
|
1215
|
+
this.insertHtml(pasteHtml);
|
|
1216
|
+
event.preventDefault();
|
|
1217
|
+
} else {
|
|
1218
|
+
let paste = clipboardData.getData("text");
|
|
1219
|
+
const selection = window.getSelection();
|
|
1220
|
+
if (!selection.rangeCount) {
|
|
1221
|
+
console.warn("handlePaste called, but unable to get selection");
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
event.preventDefault();
|
|
1225
|
+
selection.deleteFromDocument();
|
|
1226
|
+
this.insertRawText(paste);
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
componentDidMount() {
|
|
1231
|
+
try {
|
|
1232
|
+
setupKatex();
|
|
1233
|
+
|
|
1234
|
+
this.editableDiv.addEventListener("keydown", this.handleKeyDown);
|
|
1235
|
+
this.editableDiv.addEventListener("keyup", this.handleKeyUp);
|
|
1236
|
+
this.editableDiv.addEventListener("paste", this.handlePaste);
|
|
1237
|
+
if (this.props.autofocus === true) this.editableDiv.focus();
|
|
1238
|
+
} catch (error) {
|
|
1239
|
+
console.error(error);
|
|
1240
|
+
}
|
|
1299
1241
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1242
|
+
|
|
1243
|
+
componentWillUnmount() {
|
|
1244
|
+
try {
|
|
1245
|
+
this.editableDiv.removeEventListener("keydown", this.handleKeyDown);
|
|
1246
|
+
this.editableDiv.removeEventListener("keyup", this.handleKeyUp);
|
|
1247
|
+
this.editableDiv.removeEventListener("paste", this.handlePaste);
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
console.error(error);
|
|
1250
|
+
}
|
|
1309
1251
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1252
|
+
|
|
1253
|
+
componentDidUpdate() {
|
|
1254
|
+
try {
|
|
1255
|
+
// console.log("componentDidUpdate")
|
|
1256
|
+
if (
|
|
1257
|
+
this.afterComponentUpdateData !== null &&
|
|
1258
|
+
this.afterComponentUpdateData !== undefined &&
|
|
1259
|
+
this.afterComponentUpdateData.value === this.props.value
|
|
1260
|
+
) {
|
|
1261
|
+
this._setRangeParams(this.afterComponentUpdateData.rangeParams);
|
|
1262
|
+
this.afterComponentUpdateData = null;
|
|
1263
|
+
this.updateActiveButtons();
|
|
1264
|
+
}
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
console.error(error);
|
|
1267
|
+
}
|
|
1326
1268
|
}
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
showEquationEditor = (moveCursorOnInsert = false, node = null) => {
|
|
1330
|
-
try {
|
|
1331
|
-
let rangeParams = this._getRangeParams();
|
|
1332
|
-
if (rangeParams.startNodeIndex < 0) {
|
|
1333
|
-
console.error("Could not find position of cursor in node list");
|
|
1334
|
-
|
|
1335
|
-
this.setOldRangeParams({
|
|
1336
|
-
startNodeIndex: 2,
|
|
1337
|
-
startOffset: 1,
|
|
1338
|
-
endNodeIndex: 2,
|
|
1339
|
-
endOffset: 1,
|
|
1340
|
-
});
|
|
1341
|
-
this.markedRawText = MARK + " " + MARK;
|
|
1342
|
-
this.moveCursorOnInsert = moveCursorOnInsert;
|
|
1343
|
-
this.setState({
|
|
1344
|
-
showEquationEditor: true,
|
|
1345
|
-
equationEditorLatex: "",
|
|
1346
|
-
});
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
1269
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1270
|
+
showEquationEditor = (moveCursorOnInsert = false, node = null) => {
|
|
1271
|
+
try {
|
|
1272
|
+
let rangeParams = this._getRangeParams();
|
|
1273
|
+
if (rangeParams.startNodeIndex < 0) {
|
|
1274
|
+
console.error("Could not find position of cursor in node list");
|
|
1275
|
+
|
|
1276
|
+
this.setOldRangeParams({
|
|
1277
|
+
startNodeIndex: 2,
|
|
1278
|
+
startOffset: 1,
|
|
1279
|
+
endNodeIndex: 2,
|
|
1280
|
+
endOffset: 1,
|
|
1281
|
+
});
|
|
1282
|
+
this.markedRawText = MARK + " " + MARK;
|
|
1283
|
+
this.moveCursorOnInsert = moveCursorOnInsert;
|
|
1284
|
+
this.setState({
|
|
1285
|
+
showEquationEditor: true,
|
|
1286
|
+
equationEditorLatex: "",
|
|
1287
|
+
});
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1352
1290
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1291
|
+
let latex = "";
|
|
1292
|
+
let offset = 0;
|
|
1355
1293
|
|
|
1356
|
-
|
|
1357
|
-
// console.log(this.editableDiv.childNodes)
|
|
1358
|
-
// console.log(node)
|
|
1294
|
+
if (node === null) node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
1359
1295
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
handleMouseDown = (event) => {
|
|
1400
|
-
this.showEquationEditorOnMouseUp = false;
|
|
1401
|
-
let node = null;
|
|
1402
|
-
try {
|
|
1403
|
-
node = event.target;
|
|
1404
|
-
let isLatex = false;
|
|
1405
|
-
do {
|
|
1406
|
-
if (isKatexNode(node)) {
|
|
1407
|
-
isLatex = true;
|
|
1408
|
-
break;
|
|
1296
|
+
// console.log(rangeParams)
|
|
1297
|
+
// console.log(this.editableDiv.childNodes)
|
|
1298
|
+
// console.log(node)
|
|
1299
|
+
|
|
1300
|
+
if (node === null || node === undefined) {
|
|
1301
|
+
console.error("Could not find node with index: " + rangeParams.startNodeIndex);
|
|
1302
|
+
// console.log("Child nodes:");
|
|
1303
|
+
// console.log(this.editableDiv.childNodes);
|
|
1304
|
+
node = this._findFirstTextNode();
|
|
1305
|
+
offset = 0;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if (isKatexNode(node)) {
|
|
1309
|
+
latex = getLatex(node);
|
|
1310
|
+
offset = 0;
|
|
1311
|
+
this.editingExistingEquation = true;
|
|
1312
|
+
this.editingNode = node;
|
|
1313
|
+
} else {
|
|
1314
|
+
latex = "";
|
|
1315
|
+
offset = rangeParams.startOffset;
|
|
1316
|
+
this.editingExistingEquation = false;
|
|
1317
|
+
this.editingNode = null;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
this.markedRawText = elementToMarkedRawText(
|
|
1321
|
+
this.editableDiv,
|
|
1322
|
+
node,
|
|
1323
|
+
offset,
|
|
1324
|
+
this.enableHtml()
|
|
1325
|
+
);
|
|
1326
|
+
// console.log("Marked text:"+this.markedRawText)
|
|
1327
|
+
this.moveCursorOnInsert = moveCursorOnInsert;
|
|
1328
|
+
this.setState({
|
|
1329
|
+
showEquationEditor: true,
|
|
1330
|
+
equationEditorLatex: latex,
|
|
1331
|
+
});
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
console.error(error);
|
|
1409
1334
|
}
|
|
1410
|
-
|
|
1411
|
-
} while (node !== this.editableDiv && node !== null);
|
|
1335
|
+
};
|
|
1412
1336
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1337
|
+
handleMouseDown = (event) => {
|
|
1338
|
+
this.showEquationEditorOnMouseUp = false;
|
|
1339
|
+
let node = null;
|
|
1340
|
+
try {
|
|
1341
|
+
node = event.target;
|
|
1342
|
+
let isLatex = false;
|
|
1343
|
+
do {
|
|
1344
|
+
if (isKatexNode(node)) {
|
|
1345
|
+
isLatex = true;
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
node = node.parentNode;
|
|
1349
|
+
} while (node !== this.editableDiv && node !== null);
|
|
1416
1350
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1351
|
+
if (!isLatex) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1420
1354
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1355
|
+
this.showEquationEditorOnMouseUp = true;
|
|
1356
|
+
// Save current range parameters so that we can restore them later if we wish
|
|
1357
|
+
this.setOldRangeParams(this._getRangeParams());
|
|
1423
1358
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
console.error(error);
|
|
1427
|
-
console.log(node);
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1359
|
+
// this.editableDiv.focus()
|
|
1360
|
+
// this.showEquationEditor(false, node)
|
|
1430
1361
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1362
|
+
// event.preventDefault()
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
console.error(error);
|
|
1365
|
+
console.log(node);
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1434
1368
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1369
|
+
handleMouseUp = (event) => {
|
|
1370
|
+
try {
|
|
1371
|
+
this.initialiseHistory();
|
|
1438
1372
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1373
|
+
let params = this._getRangeParams();
|
|
1374
|
+
let startNode = this._findNodeWithIndex(params.startNodeIndex);
|
|
1375
|
+
let endNode = this._findNodeWithIndex(params.endNodeIndex);
|
|
1443
1376
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
) {
|
|
1449
|
-
this.editableDiv.focus();
|
|
1450
|
-
this.showEquationEditor(false, startNode);
|
|
1451
|
-
return;
|
|
1452
|
-
}
|
|
1377
|
+
if (startNode === null || endNode === null) {
|
|
1378
|
+
console.warn("Start or end node was null");
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1453
1381
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1382
|
+
if (
|
|
1383
|
+
params.startNodeIndex === params.endNodeIndex &&
|
|
1384
|
+
params.startOffset === params.endOffset &&
|
|
1385
|
+
this.showEquationEditorOnMouseUp
|
|
1386
|
+
) {
|
|
1387
|
+
this.editableDiv.focus();
|
|
1388
|
+
this.showEquationEditor(false, startNode);
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// If our range starts or ends inside a katex node, move it out
|
|
1393
|
+
let new_rangeParams = null;
|
|
1394
|
+
if (isKatexNode(startNode)) {
|
|
1395
|
+
if (new_rangeParams === null) new_rangeParams = { ...params };
|
|
1396
|
+
if (
|
|
1397
|
+
params.startNodeIndex === params.endNodeIndex &&
|
|
1398
|
+
params.startOffset === params.endOffset
|
|
1399
|
+
) {
|
|
1400
|
+
new_rangeParams.startNodeIndex = params.startNodeIndex + 1;
|
|
1401
|
+
new_rangeParams.startOffset = SMALL_SPACE_LENGTH;
|
|
1402
|
+
} else {
|
|
1403
|
+
new_rangeParams.startNodeIndex = params.startNodeIndex - 1;
|
|
1404
|
+
let prev_node = this._findNodeWithIndex(params.startNodeIndex - 1);
|
|
1405
|
+
new_rangeParams.startOffset = getNodeTextLength(prev_node);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
if (isKatexNode(endNode)) {
|
|
1409
|
+
if (new_rangeParams === null) new_rangeParams = { ...params };
|
|
1410
|
+
new_rangeParams.endNodeIndex = params.endNodeIndex + 1;
|
|
1411
|
+
new_rangeParams.endOffset = SMALL_SPACE_LENGTH;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
if (new_rangeParams !== null) {
|
|
1415
|
+
this._setRangeParams(new_rangeParams);
|
|
1416
|
+
event.preventDefault();
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
this.updateActiveButtons();
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
console.error(error);
|
|
1468
1423
|
}
|
|
1469
|
-
|
|
1470
|
-
if (isKatexNode(endNode)) {
|
|
1471
|
-
if (new_rangeParams === null) new_rangeParams = { ...params };
|
|
1472
|
-
new_rangeParams.endNodeIndex = params.endNodeIndex + 1;
|
|
1473
|
-
new_rangeParams.endOffset = SMALL_SPACE_LENGTH;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
if (new_rangeParams !== null) {
|
|
1477
|
-
this._setRangeParams(new_rangeParams);
|
|
1478
|
-
event.preventDefault();
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1424
|
+
};
|
|
1481
1425
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1426
|
+
handleEquationEditorClose = () => {
|
|
1427
|
+
try {
|
|
1428
|
+
this.setState({ showEquationEditor: false }, () => {
|
|
1429
|
+
this.editableDiv.focus();
|
|
1430
|
+
// this._setRangeParams(getOldRangeParams)
|
|
1431
|
+
});
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
console.error(error);
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1487
1436
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1437
|
+
handleEquationEditorInsert = (latex) => {
|
|
1438
|
+
try {
|
|
1439
|
+
// console.log("Inserting latex: "+latex)
|
|
1440
|
+
|
|
1441
|
+
let start_pos = this.markedRawText.indexOf(MARK);
|
|
1442
|
+
let end_pos = this.markedRawText.indexOf(MARK, start_pos + MARK.length);
|
|
1443
|
+
let oldRangeParams = this.getOldRangeParams();
|
|
1444
|
+
// console.log("Old range params")
|
|
1445
|
+
// console.log(oldRangeParams)
|
|
1446
|
+
|
|
1447
|
+
// Handle case of empty string returned
|
|
1448
|
+
if (latex === "") {
|
|
1449
|
+
// console.log("Empty latex returned from equation editor")
|
|
1450
|
+
let new_raw_text =
|
|
1451
|
+
this.markedRawText.substring(0, start_pos) +
|
|
1452
|
+
this.markedRawText.substring(end_pos + MARK.length);
|
|
1453
|
+
|
|
1454
|
+
// Delete node if it was a latex node
|
|
1455
|
+
let new_index = -1;
|
|
1456
|
+
let new_offset = -1;
|
|
1457
|
+
if (this.editingExistingEquation) {
|
|
1458
|
+
let indexOfEditedNode = this._findIndexOfNode(this.editingNode);
|
|
1459
|
+
|
|
1460
|
+
if (oldRangeParams.startNodeIndex === indexOfEditedNode + 1) {
|
|
1461
|
+
new_index = oldRangeParams.startNodeIndex - 2;
|
|
1462
|
+
new_offset = getNodeTextLength(this._findNodeWithIndex(new_index));
|
|
1463
|
+
} else if (oldRangeParams.startNodeIndex > indexOfEditedNode + 1) {
|
|
1464
|
+
new_index = oldRangeParams.startNodeIndex - 2;
|
|
1465
|
+
new_offset = oldRangeParams.startOffset;
|
|
1466
|
+
} else {
|
|
1467
|
+
new_index = oldRangeParams.startNodeIndex;
|
|
1468
|
+
new_offset = oldRangeParams.startOffset;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
let new_rangeParams = {
|
|
1473
|
+
startNodeIndex: new_index,
|
|
1474
|
+
startOffset: new_offset,
|
|
1475
|
+
endNodeIndex: new_index,
|
|
1476
|
+
endOffset: new_offset,
|
|
1477
|
+
};
|
|
1478
|
+
this.setOldRangeParams(new_rangeParams);
|
|
1479
|
+
|
|
1480
|
+
let nodeCounts = this._countNodeTypes();
|
|
1481
|
+
let mimeType = null;
|
|
1482
|
+
if (this.editingExistingEquation)
|
|
1483
|
+
mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.katex > 1);
|
|
1484
|
+
else mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.katex > 0);
|
|
1485
|
+
new_raw_text = removeEncodingIfPlainText(new_raw_text, mimeType, this.enableHtml());
|
|
1486
|
+
|
|
1487
|
+
this.applyChangesToComponent(
|
|
1488
|
+
new_raw_text,
|
|
1489
|
+
mimeType,
|
|
1490
|
+
new_rangeParams,
|
|
1491
|
+
this.props.useExpertMode,
|
|
1492
|
+
this.props.selectedTab
|
|
1493
|
+
);
|
|
1494
|
+
this.setState({ showEquationEditor: false });
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// Handle case of latex returned, in which case we update the katex node
|
|
1499
|
+
// console.log("Latex returned from equation editor: "+latex)
|
|
1500
|
+
let new_raw_text = replaceMarksWithMath(this.markedRawText, latex, this.enableHtml());
|
|
1501
|
+
|
|
1502
|
+
let new_rangeParams = this.getOldRangeParams();
|
|
1503
|
+
if (this.moveCursorOnInsert) {
|
|
1504
|
+
if (this.props.value === "")
|
|
1505
|
+
// Special case of previously empty text input
|
|
1506
|
+
new_rangeParams = {
|
|
1507
|
+
startNodeIndex: 2,
|
|
1508
|
+
startOffset: SMALL_SPACE_LENGTH,
|
|
1509
|
+
endNodeIndex: 2,
|
|
1510
|
+
endOffset: SMALL_SPACE_LENGTH,
|
|
1511
|
+
};
|
|
1512
|
+
// normal case
|
|
1513
|
+
else
|
|
1514
|
+
new_rangeParams = {
|
|
1515
|
+
startNodeIndex: oldRangeParams.startNodeIndex + 2,
|
|
1516
|
+
startOffset: SMALL_SPACE_LENGTH,
|
|
1517
|
+
endNodeIndex: oldRangeParams.startNodeIndex + 2,
|
|
1518
|
+
endOffset: SMALL_SPACE_LENGTH,
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
// console.log("New cursor pos: "+new_rangeParams.startNodeIndex+"->"+new_rangeParams.startOffset)
|
|
1522
|
+
this.setOldRangeParams(new_rangeParams);
|
|
1523
|
+
|
|
1524
|
+
let nodeCounts = this._countNodeTypes();
|
|
1525
|
+
let mimeType = this.getMimeType(nodeCounts.html > 0, true);
|
|
1526
|
+
new_raw_text = removeEncodingIfPlainText(new_raw_text, mimeType, this.enableHtml());
|
|
1527
|
+
|
|
1528
|
+
this.applyChangesToComponent(
|
|
1529
|
+
new_raw_text,
|
|
1530
|
+
mimeType,
|
|
1531
|
+
new_rangeParams,
|
|
1532
|
+
this.props.useExpertMode,
|
|
1533
|
+
this.props.selectedTab
|
|
1534
|
+
);
|
|
1535
|
+
this.setState({ showEquationEditor: false });
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
console.error(error);
|
|
1532
1538
|
}
|
|
1539
|
+
};
|
|
1533
1540
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
+
// findEditableDiv = (node) => {
|
|
1542
|
+
// if (node === null)
|
|
1543
|
+
// return null
|
|
1544
|
+
// if (node.classList !== null && node.classList !== undefined &&
|
|
1545
|
+
// node.classList.contains("MathRichInput"))
|
|
1546
|
+
// return node
|
|
1547
|
+
// return this.findEditableDiv(node.parentNode);
|
|
1548
|
+
// }
|
|
1541
1549
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1550
|
+
styleText = (event, style) => {
|
|
1551
|
+
try {
|
|
1552
|
+
style = style.toLowerCase();
|
|
1553
|
+
let success = false;
|
|
1554
|
+
// This first part is a workaround whereby executing subscript or superscript
|
|
1555
|
+
// within an existing subscript or superscript makes the text even smaller instead
|
|
1556
|
+
// of toggling it off. Thus we manually toggle it off with some jigerry pokery!
|
|
1557
|
+
if (
|
|
1558
|
+
(style === "subscript" && this.state.activeButtons.subscript) ||
|
|
1559
|
+
(style === "superscript" && this.state.activeButtons.superscript)
|
|
1560
|
+
) {
|
|
1561
|
+
const selection = document.getSelection();
|
|
1562
|
+
let range = selection.getRangeAt(0);
|
|
1563
|
+
if (
|
|
1564
|
+
range.endContainer === range.startContainer &&
|
|
1565
|
+
range.endOffset === range.startOffset
|
|
1566
|
+
) {
|
|
1567
|
+
success = document.execCommand("insertText", false, " ");
|
|
1568
|
+
range.setEnd(range.endContainer, range.endOffset + 1);
|
|
1569
|
+
selection.removeAllRanges();
|
|
1570
|
+
selection.addRange(range);
|
|
1571
|
+
}
|
|
1572
|
+
success = document.execCommand("removeFormat", false, null);
|
|
1573
|
+
if (this.state.activeButtons.bold)
|
|
1574
|
+
success = document.execCommand("bold", false, null);
|
|
1575
|
+
if (this.state.activeButtons.italic)
|
|
1576
|
+
success = document.execCommand("italic", false, null);
|
|
1577
|
+
if (this.state.activeButtons.underline)
|
|
1578
|
+
success = document.execCommand("underline", false, null);
|
|
1579
|
+
} else {
|
|
1580
|
+
// This is the normal code ...
|
|
1581
|
+
success = document.execCommand(style, false, null);
|
|
1582
|
+
}
|
|
1583
|
+
let buttonState = this.state.activeButtons[style];
|
|
1584
|
+
if (buttonState === null || buttonState === undefined) return success;
|
|
1585
|
+
|
|
1586
|
+
buttonState = !buttonState;
|
|
1587
|
+
let new_activeButtons = { ...this.state.activeButtons };
|
|
1588
|
+
new_activeButtons[style] = buttonState;
|
|
1589
|
+
this.setState({ activeButtons: new_activeButtons });
|
|
1590
|
+
return success;
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
console.error(error);
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1559
1595
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
// Handle case of latex returned, in which case we update the katex node
|
|
1572
|
-
// console.log("Latex returned from equation editor: "+latex)
|
|
1573
|
-
let new_raw_text = replaceMarksWithMath(
|
|
1574
|
-
this.markedRawText,
|
|
1575
|
-
latex,
|
|
1576
|
-
this.enableHtml()
|
|
1577
|
-
);
|
|
1578
|
-
|
|
1579
|
-
let new_rangeParams = this.getOldRangeParams();
|
|
1580
|
-
if (this.moveCursorOnInsert) {
|
|
1581
|
-
if (this.props.value === "")
|
|
1582
|
-
// Special case of previously empty text input
|
|
1583
|
-
new_rangeParams = {
|
|
1584
|
-
startNodeIndex: 2,
|
|
1585
|
-
startOffset: SMALL_SPACE_LENGTH,
|
|
1586
|
-
endNodeIndex: 2,
|
|
1587
|
-
endOffset: SMALL_SPACE_LENGTH,
|
|
1588
|
-
};
|
|
1589
|
-
// normal case
|
|
1590
|
-
else
|
|
1591
|
-
new_rangeParams = {
|
|
1592
|
-
startNodeIndex: oldRangeParams.startNodeIndex + 2,
|
|
1593
|
-
startOffset: SMALL_SPACE_LENGTH,
|
|
1594
|
-
endNodeIndex: oldRangeParams.startNodeIndex + 2,
|
|
1595
|
-
endOffset: SMALL_SPACE_LENGTH,
|
|
1596
|
-
};
|
|
1597
|
-
}
|
|
1598
|
-
// console.log("New cursor pos: "+new_rangeParams.startNodeIndex+"->"+new_rangeParams.startOffset)
|
|
1599
|
-
this.setOldRangeParams(new_rangeParams);
|
|
1600
|
-
|
|
1601
|
-
let nodeCounts = this._countNodeTypes();
|
|
1602
|
-
let mimeType = this.getMimeType(nodeCounts.html > 0, true);
|
|
1603
|
-
new_raw_text = removeEncodingIfPlainText(
|
|
1604
|
-
new_raw_text,
|
|
1605
|
-
mimeType,
|
|
1606
|
-
this.enableHtml()
|
|
1607
|
-
);
|
|
1608
|
-
|
|
1609
|
-
this.applyChangesToComponent(
|
|
1610
|
-
new_raw_text,
|
|
1611
|
-
mimeType,
|
|
1612
|
-
new_rangeParams,
|
|
1613
|
-
this.props.useExpertMode,
|
|
1614
|
-
this.props.selectedTab
|
|
1615
|
-
);
|
|
1616
|
-
this.setState({ showEquationEditor: false });
|
|
1617
|
-
} catch (error) {
|
|
1618
|
-
console.error(error);
|
|
1619
|
-
}
|
|
1620
|
-
};
|
|
1621
|
-
|
|
1622
|
-
// findEditableDiv = (node) => {
|
|
1623
|
-
// if (node === null)
|
|
1624
|
-
// return null
|
|
1625
|
-
// if (node.classList !== null && node.classList !== undefined &&
|
|
1626
|
-
// node.classList.contains("MathRichInput"))
|
|
1627
|
-
// return node
|
|
1628
|
-
// return this.findEditableDiv(node.parentNode);
|
|
1629
|
-
// }
|
|
1630
|
-
|
|
1631
|
-
styleText = (event, style) => {
|
|
1632
|
-
try {
|
|
1633
|
-
style = style.toLowerCase();
|
|
1634
|
-
let success = false;
|
|
1635
|
-
// This first part is a workaround whereby executing subscript or superscript
|
|
1636
|
-
// within an existing subscript or superscript makes the text even smaller instead
|
|
1637
|
-
// of toggling it off. Thus we manually toggle it off with some jigerry pokery!
|
|
1638
|
-
if (
|
|
1639
|
-
(style === "subscript" && this.state.activeButtons.subscript) ||
|
|
1640
|
-
(style === "superscript" && this.state.activeButtons.superscript)
|
|
1641
|
-
) {
|
|
1642
|
-
const selection = document.getSelection();
|
|
1643
|
-
let range = selection.getRangeAt(0);
|
|
1644
|
-
if (
|
|
1645
|
-
range.endContainer === range.startContainer &&
|
|
1646
|
-
range.endOffset === range.startOffset
|
|
1647
|
-
) {
|
|
1648
|
-
success = document.execCommand("insertText", false, " ");
|
|
1649
|
-
range.setEnd(range.endContainer, range.endOffset + 1);
|
|
1650
|
-
selection.removeAllRanges();
|
|
1651
|
-
selection.addRange(range);
|
|
1596
|
+
handleToolbarButtonClick = (event, buttonName) => {
|
|
1597
|
+
try {
|
|
1598
|
+
if (buttonName === "Equation") {
|
|
1599
|
+
this.showEquationEditor(true);
|
|
1600
|
+
}
|
|
1601
|
+
let style = buttonName;
|
|
1602
|
+
let success = this.styleText(event, style);
|
|
1603
|
+
if (!success) console.warn("execCommand returned false: " + style);
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
console.error(error);
|
|
1652
1606
|
}
|
|
1653
|
-
|
|
1654
|
-
if (this.state.activeButtons.bold)
|
|
1655
|
-
success = document.execCommand("bold", false, null);
|
|
1656
|
-
if (this.state.activeButtons.italic)
|
|
1657
|
-
success = document.execCommand("italic", false, null);
|
|
1658
|
-
if (this.state.activeButtons.underline)
|
|
1659
|
-
success = document.execCommand("underline", false, null);
|
|
1660
|
-
} else {
|
|
1661
|
-
// This is the normal code ...
|
|
1662
|
-
success = document.execCommand(style, false, null);
|
|
1663
|
-
}
|
|
1664
|
-
let buttonState = this.state.activeButtons[style];
|
|
1665
|
-
if (buttonState === null || buttonState === undefined) return success;
|
|
1666
|
-
|
|
1667
|
-
buttonState = !buttonState;
|
|
1668
|
-
let new_activeButtons = { ...this.state.activeButtons };
|
|
1669
|
-
new_activeButtons[style] = buttonState;
|
|
1670
|
-
this.setState({ activeButtons: new_activeButtons });
|
|
1671
|
-
return success;
|
|
1672
|
-
} catch (error) {
|
|
1673
|
-
console.error(error);
|
|
1674
|
-
}
|
|
1675
|
-
};
|
|
1607
|
+
};
|
|
1676
1608
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
1698
|
-
let offset = rangeParams.endOffset;
|
|
1699
|
-
|
|
1700
|
-
let raw_text = elementToMarkedRawText(
|
|
1701
|
-
this.editableDiv,
|
|
1702
|
-
node,
|
|
1703
|
-
offset,
|
|
1704
|
-
this.enableHtml()
|
|
1705
|
-
);
|
|
1706
|
-
|
|
1707
|
-
return getCharacterBeforeMarks(raw_text);
|
|
1708
|
-
} catch (error) {
|
|
1709
|
-
console.error(error);
|
|
1710
|
-
}
|
|
1711
|
-
};
|
|
1712
|
-
|
|
1713
|
-
insertRawText = (new_text, selectInsertedText = true) => {
|
|
1714
|
-
try {
|
|
1715
|
-
let rangeParams = this._getRangeParams();
|
|
1716
|
-
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
1717
|
-
let offset = rangeParams.endOffset;
|
|
1718
|
-
|
|
1719
|
-
let raw_text = elementToMarkedRawText(
|
|
1720
|
-
this.editableDiv,
|
|
1721
|
-
node,
|
|
1722
|
-
offset,
|
|
1723
|
-
this.enableHtml()
|
|
1724
|
-
);
|
|
1725
|
-
let new_raw_text = null;
|
|
1726
|
-
let new_rangeParams = { ...rangeParams };
|
|
1727
|
-
new_raw_text = removeMarks(
|
|
1728
|
-
insertCharacterBeforeMarks(raw_text, new_text)
|
|
1729
|
-
);
|
|
1730
|
-
|
|
1731
|
-
// console.log("Range params before insert")
|
|
1732
|
-
// console.log(rangeParams)
|
|
1733
|
-
|
|
1734
|
-
if (selectInsertedText) {
|
|
1735
|
-
new_rangeParams.startNodeIndex = rangeParams.endNodeIndex;
|
|
1736
|
-
new_rangeParams.startOffset = rangeParams.endOffset;
|
|
1737
|
-
new_rangeParams.endNodeIndex = rangeParams.endNodeIndex;
|
|
1738
|
-
new_rangeParams.endOffset = rangeParams.endOffset + new_text.length;
|
|
1739
|
-
new_rangeParams.startGlobalOffset = rangeParams.endGlobalOffset;
|
|
1740
|
-
new_rangeParams.endGlobalOffset = rangeParams.endGlobalOffset+ new_text.length;
|
|
1741
|
-
} else {
|
|
1742
|
-
new_rangeParams.startNodeIndex = rangeParams.endNodeIndex;
|
|
1743
|
-
new_rangeParams.startOffset = rangeParams.endOffset + new_text.length;
|
|
1744
|
-
new_rangeParams.endNodeIndex = rangeParams.endNodeIndex;
|
|
1745
|
-
new_rangeParams.endOffset = rangeParams.endOffset + new_text.length;
|
|
1746
|
-
new_rangeParams.startGlobalOffset = rangeParams.endGlobalOffset + new_text.length;
|
|
1747
|
-
new_rangeParams.endGlobalOffset = rangeParams.endGlobalOffset + new_text.length;
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
// console.log("Range params after insert")
|
|
1751
|
-
// console.log(new_rangeParams)
|
|
1752
|
-
|
|
1753
|
-
new_raw_text = removeEncodingIfPlainText(
|
|
1754
|
-
new_raw_text,
|
|
1755
|
-
this.props.mimeType,
|
|
1756
|
-
this.enableHtml()
|
|
1757
|
-
);
|
|
1758
|
-
this.applyChangesToComponent(
|
|
1759
|
-
new_raw_text,
|
|
1760
|
-
this.props.mimeType,
|
|
1761
|
-
new_rangeParams,
|
|
1762
|
-
this.props.useExpertMode,
|
|
1763
|
-
this.props.selectedTab
|
|
1764
|
-
);
|
|
1765
|
-
} catch (error) {
|
|
1766
|
-
console.error(error);
|
|
1767
|
-
}
|
|
1768
|
-
};
|
|
1769
|
-
|
|
1770
|
-
// https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
|
|
1771
|
-
pasteHtmlAtCaret = (html, selectPastedContent) => {
|
|
1772
|
-
var sel, range;
|
|
1773
|
-
if (window.getSelection) {
|
|
1774
|
-
// IE9 and non-IE
|
|
1775
|
-
sel = window.getSelection();
|
|
1776
|
-
if (sel.getRangeAt && sel.rangeCount) {
|
|
1777
|
-
range = sel.getRangeAt(0);
|
|
1778
|
-
range.deleteContents();
|
|
1779
|
-
// Range.createContextualFragment() would be useful here but is
|
|
1780
|
-
// only relatively recently standardized and is not supported in
|
|
1781
|
-
// some browsers (IE9, for one)
|
|
1782
|
-
var el = document.createElement("div");
|
|
1783
|
-
el.innerHTML = html;
|
|
1784
|
-
var frag = document.createDocumentFragment(),
|
|
1785
|
-
node,
|
|
1786
|
-
lastNode;
|
|
1787
|
-
while ((node = el.firstChild)) {
|
|
1788
|
-
lastNode = frag.appendChild(node);
|
|
1609
|
+
fetchAccentLetter = (event) => {
|
|
1610
|
+
try {
|
|
1611
|
+
let rangeParams = this._getRangeParams();
|
|
1612
|
+
if (rangeParams === null) {
|
|
1613
|
+
console.error("Cound not get range params");
|
|
1614
|
+
return null;
|
|
1615
|
+
}
|
|
1616
|
+
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
1617
|
+
let offset = rangeParams.endOffset;
|
|
1618
|
+
|
|
1619
|
+
let raw_text = elementToMarkedRawText(
|
|
1620
|
+
this.editableDiv,
|
|
1621
|
+
node,
|
|
1622
|
+
offset,
|
|
1623
|
+
this.enableHtml()
|
|
1624
|
+
);
|
|
1625
|
+
|
|
1626
|
+
return getCharacterBeforeMarks(raw_text);
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
console.error(error);
|
|
1789
1629
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
insertRawText = (new_text, selectInsertedText = true) => {
|
|
1633
|
+
try {
|
|
1634
|
+
let rangeParams = this._getRangeParams();
|
|
1635
|
+
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
1636
|
+
let offset = rangeParams.endOffset;
|
|
1637
|
+
|
|
1638
|
+
let raw_text = elementToMarkedRawText(
|
|
1639
|
+
this.editableDiv,
|
|
1640
|
+
node,
|
|
1641
|
+
offset,
|
|
1642
|
+
this.enableHtml()
|
|
1643
|
+
);
|
|
1644
|
+
let new_raw_text = null;
|
|
1645
|
+
let new_rangeParams = { ...rangeParams };
|
|
1646
|
+
new_raw_text = removeMarks(insertCharacterBeforeMarks(raw_text, new_text));
|
|
1647
|
+
|
|
1648
|
+
// console.log("Range params before insert")
|
|
1649
|
+
// console.log(rangeParams)
|
|
1650
|
+
|
|
1651
|
+
if (selectInsertedText) {
|
|
1652
|
+
new_rangeParams.startNodeIndex = rangeParams.endNodeIndex;
|
|
1653
|
+
new_rangeParams.startOffset = rangeParams.endOffset;
|
|
1654
|
+
new_rangeParams.endNodeIndex = rangeParams.endNodeIndex;
|
|
1655
|
+
new_rangeParams.endOffset = rangeParams.endOffset + new_text.length;
|
|
1656
|
+
new_rangeParams.startGlobalOffset = rangeParams.endGlobalOffset;
|
|
1657
|
+
new_rangeParams.endGlobalOffset = rangeParams.endGlobalOffset + new_text.length;
|
|
1658
|
+
} else {
|
|
1659
|
+
new_rangeParams.startNodeIndex = rangeParams.endNodeIndex;
|
|
1660
|
+
new_rangeParams.startOffset = rangeParams.endOffset + new_text.length;
|
|
1661
|
+
new_rangeParams.endNodeIndex = rangeParams.endNodeIndex;
|
|
1662
|
+
new_rangeParams.endOffset = rangeParams.endOffset + new_text.length;
|
|
1663
|
+
new_rangeParams.startGlobalOffset = rangeParams.endGlobalOffset + new_text.length;
|
|
1664
|
+
new_rangeParams.endGlobalOffset = rangeParams.endGlobalOffset + new_text.length;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// console.log("Range params after insert")
|
|
1668
|
+
// console.log(new_rangeParams)
|
|
1669
|
+
|
|
1670
|
+
new_raw_text = removeEncodingIfPlainText(
|
|
1671
|
+
new_raw_text,
|
|
1672
|
+
this.props.mimeType,
|
|
1673
|
+
this.enableHtml()
|
|
1674
|
+
);
|
|
1675
|
+
this.applyChangesToComponent(
|
|
1676
|
+
new_raw_text,
|
|
1677
|
+
this.props.mimeType,
|
|
1678
|
+
new_rangeParams,
|
|
1679
|
+
this.props.useExpertMode,
|
|
1680
|
+
this.props.selectedTab
|
|
1681
|
+
);
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
console.error(error);
|
|
1803
1684
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
let i = -1;
|
|
1868
|
-
do {
|
|
1869
|
-
i = new_raw_text.indexOf("<P>", i + 1);
|
|
1870
|
-
if (i >= 0) {
|
|
1871
|
-
let j = new_raw_text.indexOf("<P>", i + 1);
|
|
1872
|
-
let k = new_raw_text.indexOf("</P>", i + 1);
|
|
1873
|
-
if (j >= 0) {
|
|
1874
|
-
if (k < 0 || k > j) {
|
|
1875
|
-
new_raw_text =
|
|
1876
|
-
new_raw_text.substring(0, j) +
|
|
1877
|
-
new_raw_text.substring(j + 3, new_raw_text.length);
|
|
1878
|
-
i = -1;
|
|
1879
|
-
}
|
|
1685
|
+
};
|
|
1686
|
+
|
|
1687
|
+
// https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
|
|
1688
|
+
pasteHtmlAtCaret = (html, selectPastedContent) => {
|
|
1689
|
+
var sel, range;
|
|
1690
|
+
if (window.getSelection) {
|
|
1691
|
+
// IE9 and non-IE
|
|
1692
|
+
sel = window.getSelection();
|
|
1693
|
+
if (sel.getRangeAt && sel.rangeCount) {
|
|
1694
|
+
range = sel.getRangeAt(0);
|
|
1695
|
+
range.deleteContents();
|
|
1696
|
+
// Range.createContextualFragment() would be useful here but is
|
|
1697
|
+
// only relatively recently standardized and is not supported in
|
|
1698
|
+
// some browsers (IE9, for one)
|
|
1699
|
+
var el = document.createElement("div");
|
|
1700
|
+
el.innerHTML = html;
|
|
1701
|
+
var frag = document.createDocumentFragment(),
|
|
1702
|
+
node,
|
|
1703
|
+
lastNode;
|
|
1704
|
+
while ((node = el.firstChild)) {
|
|
1705
|
+
lastNode = frag.appendChild(node);
|
|
1706
|
+
}
|
|
1707
|
+
var firstNode = frag.firstChild;
|
|
1708
|
+
range.insertNode(frag);
|
|
1709
|
+
// Preserve the selection
|
|
1710
|
+
if (lastNode) {
|
|
1711
|
+
range = range.cloneRange();
|
|
1712
|
+
range.setStartAfter(lastNode);
|
|
1713
|
+
if (selectPastedContent) {
|
|
1714
|
+
range.setStartBefore(firstNode);
|
|
1715
|
+
} else {
|
|
1716
|
+
range.collapse(true);
|
|
1717
|
+
}
|
|
1718
|
+
sel.removeAllRanges();
|
|
1719
|
+
sel.addRange(range);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
} else if ((sel = document.selection) && sel.type != "Control") {
|
|
1723
|
+
// IE < 9
|
|
1724
|
+
var originalRange = sel.createRange();
|
|
1725
|
+
originalRange.collapse(true);
|
|
1726
|
+
sel.createRange().pasteHTML(html);
|
|
1727
|
+
if (selectPastedContent) {
|
|
1728
|
+
range = sel.createRange();
|
|
1729
|
+
range.setEndPoint("StartToStart", originalRange);
|
|
1730
|
+
range.select();
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
insertHtml = (new_html) => {
|
|
1736
|
+
try {
|
|
1737
|
+
// console.log("New html: "+new_html)
|
|
1738
|
+
// console.log("inserting: "+new_html)
|
|
1739
|
+
this.pasteHtmlAtCaret(new_html, true);
|
|
1740
|
+
let new_rangeParams = null;
|
|
1741
|
+
// console.log("New range params: ")
|
|
1742
|
+
try {
|
|
1743
|
+
new_rangeParams = this._getRangeParams();
|
|
1744
|
+
// console.log("New range params: ")
|
|
1745
|
+
// console.log(new_rangeParams)
|
|
1746
|
+
} catch (error) {
|
|
1747
|
+
console.error(error);
|
|
1880
1748
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1749
|
+
// TODO: Fix the bug that fails to get range params correctly
|
|
1750
|
+
if (new_rangeParams.startNodeIndex < 0 || new_rangeParams.endNodeIndex < 0) {
|
|
1751
|
+
console.warn(
|
|
1752
|
+
"Selection range params were not fetched correctly. Unable to set highlight inserted text."
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
// Even if fetches valid range params, they are probably incorrect
|
|
1756
|
+
// So lets clear them for now
|
|
1757
|
+
new_rangeParams = {
|
|
1758
|
+
startNodeIndex: 0,
|
|
1759
|
+
startOffset: 0,
|
|
1760
|
+
endNodeIndex: 0,
|
|
1761
|
+
endOffset: 0,
|
|
1762
|
+
};
|
|
1763
|
+
let new_raw_text = elementToMarkedRawText(
|
|
1764
|
+
this.editableDiv,
|
|
1765
|
+
null,
|
|
1766
|
+
-1,
|
|
1767
|
+
this.enableHtml()
|
|
1768
|
+
);
|
|
1769
|
+
// console.log("New raw text:"+new_raw_text)
|
|
1770
|
+
let nodeCounts = this._countNodeTypes();
|
|
1771
|
+
let mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.math > 0);
|
|
1772
|
+
new_raw_text = removeEncodingIfPlainText(new_raw_text, mimeType, this.enableHtml());
|
|
1773
|
+
// OK, got to remove the double <p> and double </p> inserted
|
|
1774
|
+
// After inserting <p>a</p><p>b</p> into <p>xy</p> we will have
|
|
1775
|
+
// <P>x<P>a</P><P>b</P>y</P> and we want <P>xa</P><P>by</P>
|
|
1776
|
+
try {
|
|
1777
|
+
let i = -1;
|
|
1778
|
+
do {
|
|
1779
|
+
i = new_raw_text.indexOf("<P>", i + 1);
|
|
1780
|
+
if (i >= 0) {
|
|
1781
|
+
let j = new_raw_text.indexOf("<P>", i + 1);
|
|
1782
|
+
let k = new_raw_text.indexOf("</P>", i + 1);
|
|
1783
|
+
if (j >= 0) {
|
|
1784
|
+
if (k < 0 || k > j) {
|
|
1785
|
+
new_raw_text =
|
|
1786
|
+
new_raw_text.substring(0, j) +
|
|
1787
|
+
new_raw_text.substring(j + 3, new_raw_text.length);
|
|
1788
|
+
i = -1;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
} while (i >= 0 && i < new_raw_text.length - 4);
|
|
1894
1793
|
i = -1;
|
|
1895
|
-
|
|
1794
|
+
do {
|
|
1795
|
+
i = new_raw_text.indexOf("</P>", i + 1);
|
|
1796
|
+
if (i >= 0) {
|
|
1797
|
+
let j = new_raw_text.indexOf("</P>", i + 1);
|
|
1798
|
+
let k = new_raw_text.indexOf("<P>", i + 1);
|
|
1799
|
+
if (j >= 0) {
|
|
1800
|
+
if (k < 0 || k > j) {
|
|
1801
|
+
new_raw_text =
|
|
1802
|
+
new_raw_text.substring(0, i) +
|
|
1803
|
+
new_raw_text.substring(i + 4, new_raw_text.length);
|
|
1804
|
+
i = -1;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
} while (i >= 0 && i < new_raw_text.length - 4);
|
|
1809
|
+
} catch (error) {
|
|
1810
|
+
console.error(error);
|
|
1896
1811
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
this.props.useExpertMode,
|
|
1908
|
-
this.props.selectedTab
|
|
1909
|
-
);
|
|
1910
|
-
} catch (error) {
|
|
1911
|
-
console.error(error);
|
|
1912
|
-
}
|
|
1913
|
-
};
|
|
1914
|
-
|
|
1915
|
-
removeNewLine = () => {
|
|
1916
|
-
try {
|
|
1917
|
-
let rangeParams = this._getRangeParams();
|
|
1918
|
-
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
1919
|
-
let prevNode = this._findNodeWithIndex(rangeParams.endNodeIndex - 1);
|
|
1920
|
-
let offset = rangeParams.endOffset;
|
|
1921
|
-
|
|
1922
|
-
let raw_text = elementToMarkedRawText(
|
|
1923
|
-
this.editableDiv,
|
|
1924
|
-
node,
|
|
1925
|
-
offset,
|
|
1926
|
-
this.enableHtml()
|
|
1927
|
-
);
|
|
1928
|
-
let new_raw_text = null;
|
|
1929
|
-
new_raw_text = removeMarks(
|
|
1930
|
-
replaceStringBeforeMarks(raw_text, "</p><p>", "")
|
|
1931
|
-
);
|
|
1932
|
-
|
|
1933
|
-
let new_rangeParams = { ...rangeParams };
|
|
1934
|
-
let new_offset = getNodeTextLength(prevNode);
|
|
1935
|
-
new_rangeParams.startNodeIndex = rangeParams.startNodeIndex - 1;
|
|
1936
|
-
new_rangeParams.startOffset = new_offset;
|
|
1937
|
-
new_rangeParams.endNodeIndex = rangeParams.endNodeIndex - 1;
|
|
1938
|
-
new_rangeParams.endOffset = new_offset;
|
|
1939
|
-
new_rangeParams.srartGlobalOffset--;
|
|
1940
|
-
new_rangeParams.endGlobalOffset--;
|
|
1941
|
-
|
|
1942
|
-
new_raw_text = removeEncodingIfPlainText(
|
|
1943
|
-
new_raw_text,
|
|
1944
|
-
this.props.mimeType,
|
|
1945
|
-
this.enableHtml()
|
|
1946
|
-
);
|
|
1947
|
-
|
|
1948
|
-
this.applyChangesToComponent(
|
|
1949
|
-
new_raw_text,
|
|
1950
|
-
this.props.mimeType,
|
|
1951
|
-
new_rangeParams,
|
|
1952
|
-
this.props.useExpertMode,
|
|
1953
|
-
this.props.selectedTab
|
|
1954
|
-
);
|
|
1955
|
-
} catch (error) {
|
|
1956
|
-
console.error(error);
|
|
1957
|
-
}
|
|
1958
|
-
};
|
|
1959
|
-
|
|
1960
|
-
insertNewLine = () => {
|
|
1961
|
-
try {
|
|
1962
|
-
let rangeParams = this._getRangeParams();
|
|
1963
|
-
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
1964
|
-
let offset = rangeParams.endOffset;
|
|
1965
|
-
|
|
1966
|
-
let raw_text = elementToMarkedRawText(
|
|
1967
|
-
this.editableDiv,
|
|
1968
|
-
node,
|
|
1969
|
-
offset,
|
|
1970
|
-
this.enableHtml()
|
|
1971
|
-
);
|
|
1972
|
-
|
|
1973
|
-
// console.log("Raw text before line insert")
|
|
1974
|
-
// console.log(raw_text)
|
|
1975
|
-
// console.log("Range params before line insert")
|
|
1976
|
-
// console.log(rangeParams)
|
|
1977
|
-
|
|
1978
|
-
let new_raw_text = null;
|
|
1979
|
-
let new_rangeParams = { ...rangeParams };
|
|
1980
|
-
|
|
1981
|
-
if (this.enableHtml()) {
|
|
1982
|
-
new_raw_text = removeMarks(
|
|
1983
|
-
insertCharacterBeforeMarks(raw_text, "</p><p>")
|
|
1984
|
-
);
|
|
1985
|
-
if (new_raw_text.substring(0, 3).toLowerCase() !== "<p>")
|
|
1986
|
-
new_raw_text = "<p>" + new_raw_text + "</p>";
|
|
1987
|
-
|
|
1988
|
-
new_rangeParams.startNodeIndex = rangeParams.startNodeIndex + 1;
|
|
1989
|
-
new_rangeParams.startOffset = 0;
|
|
1990
|
-
new_rangeParams.endNodeIndex = rangeParams.endNodeIndex + 1;
|
|
1991
|
-
new_rangeParams.endOffset = 0;
|
|
1992
|
-
if (new_rangeParams.startGlobalOffset) {
|
|
1993
|
-
new_rangeParams.startGlobalOffset++;
|
|
1994
|
-
new_rangeParams.endGlobalOffset++;
|
|
1812
|
+
// console.log("New raw text:"+new_raw_text)
|
|
1813
|
+
this.applyChangesToComponent(
|
|
1814
|
+
new_raw_text,
|
|
1815
|
+
mimeType,
|
|
1816
|
+
new_rangeParams,
|
|
1817
|
+
this.props.useExpertMode,
|
|
1818
|
+
this.props.selectedTab
|
|
1819
|
+
);
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
console.error(error);
|
|
1995
1822
|
}
|
|
1996
|
-
|
|
1997
|
-
} else {
|
|
1998
|
-
new_raw_text = removeMarks(insertCharacterBeforeMarks(raw_text, "\n"));
|
|
1999
|
-
new_rangeParams.startOffset++;
|
|
2000
|
-
new_rangeParams.endOffset++;
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
new_raw_text = removeEncodingIfPlainText(
|
|
2004
|
-
new_raw_text,
|
|
2005
|
-
this.props.mimeType,
|
|
2006
|
-
this.enableHtml()
|
|
2007
|
-
);
|
|
2008
|
-
|
|
2009
|
-
// console.log("New raw text after line insert")
|
|
2010
|
-
// console.log(new_raw_text)
|
|
2011
|
-
// console.log("New range params after line insert")
|
|
2012
|
-
// console.log(new_rangeParams)
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
this.applyChangesToComponent(
|
|
2016
|
-
new_raw_text,
|
|
2017
|
-
this.props.mimeType,
|
|
2018
|
-
new_rangeParams,
|
|
2019
|
-
this.props.useExpertMode,
|
|
2020
|
-
this.props.selectedTab
|
|
2021
|
-
);
|
|
2022
|
-
} catch (error) {
|
|
2023
|
-
console.error(error);
|
|
2024
|
-
}
|
|
2025
|
-
};
|
|
2026
|
-
|
|
2027
|
-
insertAfterSmallSpace(new_char, rangeParams) {
|
|
2028
|
-
try {
|
|
2029
|
-
if (rangeParams === null || rangeParams === undefined)
|
|
2030
|
-
rangeParams = this._getRangeParams();
|
|
2031
|
-
|
|
2032
|
-
if (rangeParams.startOffset !== 0)
|
|
2033
|
-
console.warn("Unexpected startOffset "+rangeParams.startOffset+" in insertAfterSmallSpace")
|
|
2034
|
-
|
|
2035
|
-
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
2036
|
-
let raw_text = elementToMarkedRawText(
|
|
2037
|
-
this.editableDiv,
|
|
2038
|
-
node,
|
|
2039
|
-
1, // after small space
|
|
2040
|
-
this.enableHtml()
|
|
2041
|
-
);
|
|
1823
|
+
};
|
|
2042
1824
|
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
this.enableHtml()
|
|
2084
|
-
);
|
|
2085
|
-
|
|
2086
|
-
let new_raw_text = null;
|
|
2087
|
-
let new_rangeParams = null;
|
|
2088
|
-
|
|
2089
|
-
if (old_char === null) { // insert new char
|
|
2090
|
-
new_raw_text = removeMarks(insertCharacterBeforeMarks(raw_text, new_char));
|
|
2091
|
-
new_rangeParams = {
|
|
2092
|
-
startNodeIndex:rangeParams.startNodeIndex,
|
|
2093
|
-
startOffset:rangeParams.startOffset + new_char.length,
|
|
2094
|
-
endNodeIndex:rangeParams.startNodeIndex,
|
|
2095
|
-
endOffset:rangeParams.startOffset + new_char.length,
|
|
2096
|
-
startGlobalOffset:rangeParams.startGlobalOffset + new_char.length,
|
|
2097
|
-
endGlobalOffset:rangeParams.startGlobalOffset + new_char.length
|
|
1825
|
+
removeNewLine = () => {
|
|
1826
|
+
try {
|
|
1827
|
+
let rangeParams = this._getRangeParams();
|
|
1828
|
+
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
1829
|
+
let prevNode = this._findNodeWithIndex(rangeParams.endNodeIndex - 1);
|
|
1830
|
+
let offset = rangeParams.endOffset;
|
|
1831
|
+
|
|
1832
|
+
let raw_text = elementToMarkedRawText(
|
|
1833
|
+
this.editableDiv,
|
|
1834
|
+
node,
|
|
1835
|
+
offset,
|
|
1836
|
+
this.enableHtml()
|
|
1837
|
+
);
|
|
1838
|
+
let new_raw_text = null;
|
|
1839
|
+
new_raw_text = removeMarks(replaceStringBeforeMarks(raw_text, "</p><p>", ""));
|
|
1840
|
+
|
|
1841
|
+
let new_rangeParams = { ...rangeParams };
|
|
1842
|
+
let new_offset = getNodeTextLength(prevNode);
|
|
1843
|
+
new_rangeParams.startNodeIndex = rangeParams.startNodeIndex - 1;
|
|
1844
|
+
new_rangeParams.startOffset = new_offset;
|
|
1845
|
+
new_rangeParams.endNodeIndex = rangeParams.endNodeIndex - 1;
|
|
1846
|
+
new_rangeParams.endOffset = new_offset;
|
|
1847
|
+
new_rangeParams.srartGlobalOffset--;
|
|
1848
|
+
new_rangeParams.endGlobalOffset--;
|
|
1849
|
+
|
|
1850
|
+
new_raw_text = removeEncodingIfPlainText(
|
|
1851
|
+
new_raw_text,
|
|
1852
|
+
this.props.mimeType,
|
|
1853
|
+
this.enableHtml()
|
|
1854
|
+
);
|
|
1855
|
+
|
|
1856
|
+
this.applyChangesToComponent(
|
|
1857
|
+
new_raw_text,
|
|
1858
|
+
this.props.mimeType,
|
|
1859
|
+
new_rangeParams,
|
|
1860
|
+
this.props.useExpertMode,
|
|
1861
|
+
this.props.selectedTab
|
|
1862
|
+
);
|
|
1863
|
+
} catch (error) {
|
|
1864
|
+
console.error(error);
|
|
2098
1865
|
}
|
|
2099
|
-
|
|
2100
|
-
new_raw_text = removeMarks(replaceCharacterBeforeMarks(raw_text, new_char));
|
|
2101
|
-
new_rangeParams = { ...rangeParams };
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
new_raw_text = removeEncodingIfPlainText(
|
|
2105
|
-
new_raw_text,
|
|
2106
|
-
this.props.mimeType,
|
|
2107
|
-
this.enableHtml()
|
|
2108
|
-
);
|
|
2109
|
-
|
|
2110
|
-
this.applyChangesToComponent(
|
|
2111
|
-
new_raw_text,
|
|
2112
|
-
this.props.mimeType,
|
|
2113
|
-
new_rangeParams,
|
|
2114
|
-
this.props.useExpertMode,
|
|
2115
|
-
this.props.selectedTab
|
|
2116
|
-
);
|
|
2117
|
-
} catch (error) {
|
|
2118
|
-
console.error(error);
|
|
2119
|
-
}
|
|
2120
|
-
};
|
|
2121
|
-
|
|
2122
|
-
handleAccentButtonClick = (event, oldCharacter, newCharacter) => {
|
|
2123
|
-
try {
|
|
2124
|
-
this.insertAccentLetter(event, oldCharacter, newCharacter);
|
|
2125
|
-
console.log("Hiding accent bar 3")
|
|
2126
|
-
this.setState({ showAccentBar: false });
|
|
2127
|
-
} catch (error) {
|
|
2128
|
-
console.error(error);
|
|
2129
|
-
}
|
|
2130
|
-
};
|
|
1866
|
+
};
|
|
2131
1867
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
1868
|
+
insertNewLine = () => {
|
|
1869
|
+
try {
|
|
1870
|
+
let rangeParams = this._getRangeParams();
|
|
1871
|
+
let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
|
|
1872
|
+
let offset = rangeParams.endOffset;
|
|
1873
|
+
|
|
1874
|
+
let raw_text = elementToMarkedRawText(
|
|
1875
|
+
this.editableDiv,
|
|
1876
|
+
node,
|
|
1877
|
+
offset,
|
|
1878
|
+
this.enableHtml()
|
|
1879
|
+
);
|
|
1880
|
+
|
|
1881
|
+
// console.log("Raw text before line insert")
|
|
1882
|
+
// console.log(raw_text)
|
|
1883
|
+
// console.log("Range params before line insert")
|
|
1884
|
+
// console.log(rangeParams)
|
|
1885
|
+
|
|
1886
|
+
let new_raw_text = null;
|
|
1887
|
+
let new_rangeParams = { ...rangeParams };
|
|
1888
|
+
|
|
1889
|
+
if (this.enableHtml()) {
|
|
1890
|
+
new_raw_text = removeMarks(insertCharacterBeforeMarks(raw_text, "</p><p>"));
|
|
1891
|
+
if (new_raw_text.substring(0, 3).toLowerCase() !== "<p>")
|
|
1892
|
+
new_raw_text = "<p>" + new_raw_text + "</p>";
|
|
1893
|
+
|
|
1894
|
+
new_rangeParams.startNodeIndex = rangeParams.startNodeIndex + 1;
|
|
1895
|
+
new_rangeParams.startOffset = 0;
|
|
1896
|
+
new_rangeParams.endNodeIndex = rangeParams.endNodeIndex + 1;
|
|
1897
|
+
new_rangeParams.endOffset = 0;
|
|
1898
|
+
if (new_rangeParams.startGlobalOffset) {
|
|
1899
|
+
new_rangeParams.startGlobalOffset++;
|
|
1900
|
+
new_rangeParams.endGlobalOffset++;
|
|
1901
|
+
}
|
|
1902
|
+
} else {
|
|
1903
|
+
new_raw_text = removeMarks(insertCharacterBeforeMarks(raw_text, "\n"));
|
|
1904
|
+
new_rangeParams.startOffset++;
|
|
1905
|
+
new_rangeParams.endOffset++;
|
|
1906
|
+
}
|
|
2135
1907
|
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
try {
|
|
2142
|
-
this._setRangeParams(this.getOldRangeParams());
|
|
2143
|
-
this.setState({ hasFocus: true });
|
|
2144
|
-
} catch (error) {
|
|
2145
|
-
console.error(error);
|
|
2146
|
-
}
|
|
2147
|
-
};
|
|
1908
|
+
new_raw_text = removeEncodingIfPlainText(
|
|
1909
|
+
new_raw_text,
|
|
1910
|
+
this.props.mimeType,
|
|
1911
|
+
this.enableHtml()
|
|
1912
|
+
);
|
|
2148
1913
|
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
1914
|
+
// console.log("New raw text after line insert")
|
|
1915
|
+
// console.log(new_raw_text)
|
|
1916
|
+
// console.log("New range params after line insert")
|
|
1917
|
+
// console.log(new_rangeParams)
|
|
1918
|
+
|
|
1919
|
+
this.applyChangesToComponent(
|
|
1920
|
+
new_raw_text,
|
|
1921
|
+
this.props.mimeType,
|
|
1922
|
+
new_rangeParams,
|
|
1923
|
+
this.props.useExpertMode,
|
|
1924
|
+
this.props.selectedTab
|
|
1925
|
+
);
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
console.error(error);
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
|
|
1931
|
+
insertAfterSmallSpace(new_char, rangeParams) {
|
|
1932
|
+
try {
|
|
1933
|
+
if (rangeParams === null || rangeParams === undefined)
|
|
1934
|
+
rangeParams = this._getRangeParams();
|
|
1935
|
+
|
|
1936
|
+
if (rangeParams.startOffset !== 0)
|
|
1937
|
+
console.warn(
|
|
1938
|
+
"Unexpected startOffset " +
|
|
1939
|
+
rangeParams.startOffset +
|
|
1940
|
+
" in insertAfterSmallSpace"
|
|
1941
|
+
);
|
|
2161
1942
|
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
1943
|
+
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
1944
|
+
let raw_text = elementToMarkedRawText(
|
|
1945
|
+
this.editableDiv,
|
|
1946
|
+
node,
|
|
1947
|
+
1, // after small space
|
|
1948
|
+
this.enableHtml()
|
|
1949
|
+
);
|
|
2165
1950
|
|
|
2166
|
-
|
|
2167
|
-
this.isComposing = false;
|
|
2168
|
-
}
|
|
1951
|
+
let new_raw_text = removeMarks(insertCharacterBeforeMarks(raw_text, new_char));
|
|
2169
1952
|
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
1953
|
+
let new_rangeParams = {
|
|
1954
|
+
startNodeIndex: rangeParams.startNodeIndex,
|
|
1955
|
+
startNodeOffset: 1 + new_char.length,
|
|
1956
|
+
endNodeIndex: rangeParams.startNodeIndex,
|
|
1957
|
+
endNodeOffset: 1 + new_char.length,
|
|
1958
|
+
startGlobalOffset: rangeParams.startGlobalOffset + new_char.length,
|
|
1959
|
+
endGlobalOffset: rangeParams.endGlobalOffset + new_char.length,
|
|
1960
|
+
};
|
|
1961
|
+
|
|
1962
|
+
new_raw_text = removeEncodingIfPlainText(
|
|
1963
|
+
new_raw_text,
|
|
1964
|
+
this.props.mimeType,
|
|
1965
|
+
this.enableHtml()
|
|
1966
|
+
);
|
|
1967
|
+
|
|
1968
|
+
this.applyChangesToComponent(
|
|
1969
|
+
new_raw_text,
|
|
1970
|
+
this.props.mimeType,
|
|
1971
|
+
new_rangeParams,
|
|
1972
|
+
this.props.useExpertMode,
|
|
1973
|
+
this.props.selectedTab
|
|
1974
|
+
);
|
|
1975
|
+
} catch (error) {
|
|
1976
|
+
console.error(error);
|
|
1977
|
+
}
|
|
2174
1978
|
}
|
|
2175
1979
|
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
1980
|
+
insertAccentLetter = (event, old_char, new_char) => {
|
|
1981
|
+
try {
|
|
1982
|
+
let rangeParams = this._getRangeParams();
|
|
1983
|
+
let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
|
|
1984
|
+
let offset = rangeParams.startOffset;
|
|
1985
|
+
|
|
1986
|
+
let raw_text = elementToMarkedRawText(
|
|
1987
|
+
this.editableDiv,
|
|
1988
|
+
node,
|
|
1989
|
+
offset,
|
|
1990
|
+
this.enableHtml()
|
|
1991
|
+
);
|
|
1992
|
+
|
|
1993
|
+
let new_raw_text = null;
|
|
1994
|
+
let new_rangeParams = null;
|
|
1995
|
+
|
|
1996
|
+
if (old_char === null) {
|
|
1997
|
+
// insert new char
|
|
1998
|
+
new_raw_text = removeMarks(insertCharacterBeforeMarks(raw_text, new_char));
|
|
1999
|
+
new_rangeParams = {
|
|
2000
|
+
startNodeIndex: rangeParams.startNodeIndex,
|
|
2001
|
+
startOffset: rangeParams.startOffset + new_char.length,
|
|
2002
|
+
endNodeIndex: rangeParams.startNodeIndex,
|
|
2003
|
+
endOffset: rangeParams.startOffset + new_char.length,
|
|
2004
|
+
startGlobalOffset: rangeParams.startGlobalOffset + new_char.length,
|
|
2005
|
+
endGlobalOffset: rangeParams.startGlobalOffset + new_char.length,
|
|
2006
|
+
};
|
|
2007
|
+
} else {
|
|
2008
|
+
// replace old char
|
|
2009
|
+
new_raw_text = removeMarks(replaceCharacterBeforeMarks(raw_text, new_char));
|
|
2010
|
+
new_rangeParams = { ...rangeParams };
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
new_raw_text = removeEncodingIfPlainText(
|
|
2014
|
+
new_raw_text,
|
|
2015
|
+
this.props.mimeType,
|
|
2016
|
+
this.enableHtml()
|
|
2017
|
+
);
|
|
2018
|
+
|
|
2019
|
+
this.applyChangesToComponent(
|
|
2020
|
+
new_raw_text,
|
|
2021
|
+
this.props.mimeType,
|
|
2022
|
+
new_rangeParams,
|
|
2023
|
+
this.props.useExpertMode,
|
|
2024
|
+
this.props.selectedTab
|
|
2025
|
+
);
|
|
2026
|
+
} catch (error) {
|
|
2027
|
+
console.error(error);
|
|
2028
|
+
}
|
|
2029
|
+
};
|
|
2030
|
+
|
|
2031
|
+
handleAccentButtonClick = (event, oldCharacter, newCharacter) => {
|
|
2032
|
+
try {
|
|
2033
|
+
this.insertAccentLetter(event, oldCharacter, newCharacter);
|
|
2034
|
+
console.log("Hiding accent bar 3");
|
|
2035
|
+
this.setState({ showAccentBar: false });
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
console.error(error);
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
focus = () => {
|
|
2042
|
+
this.editableDiv.focus();
|
|
2043
|
+
};
|
|
2044
|
+
|
|
2045
|
+
handleFocus = (event) => {
|
|
2046
|
+
const { onFocus } = this.props;
|
|
2047
|
+
if (onFocus != null) {
|
|
2048
|
+
onFocus();
|
|
2049
|
+
}
|
|
2050
|
+
try {
|
|
2051
|
+
this._setRangeParams(this.getOldRangeParams());
|
|
2052
|
+
this.setState({ hasFocus: true });
|
|
2053
|
+
} catch (error) {
|
|
2054
|
+
console.error(error);
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
|
|
2058
|
+
handleBlur = (event) => {
|
|
2059
|
+
const { onBlur } = this.props;
|
|
2060
|
+
if (onBlur != null) {
|
|
2061
|
+
onBlur();
|
|
2062
|
+
}
|
|
2063
|
+
try {
|
|
2064
|
+
console.log("Hiding accent bar 4");
|
|
2065
|
+
this.setState({ hasFocus: false, showAccentBar: false });
|
|
2066
|
+
} catch (error) {
|
|
2067
|
+
console.error(error);
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
2070
|
+
|
|
2071
|
+
handleCompositionStart = (event) => {
|
|
2072
|
+
this.isComposing = true;
|
|
2073
|
+
};
|
|
2074
|
+
|
|
2075
|
+
handleCompositionEnd = (event) => {
|
|
2076
|
+
this.isComposing = false;
|
|
2077
|
+
};
|
|
2078
|
+
|
|
2079
|
+
render() {
|
|
2080
|
+
if (this.props.value == null) {
|
|
2081
|
+
console.error('MathRichInput must have prop called "value"');
|
|
2082
|
+
console.log(this.props);
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
const html = rawTextToHtml(katex, this.props.value || "", this.props.mimeType);
|
|
2086
|
+
|
|
2087
|
+
let useClassName = "MathRichInput-Container"; // Don't change this name - used by various components to find node
|
|
2088
|
+
if (this.props.className) useClassName = this.props.className + " " + useClassName;
|
|
2089
|
+
|
|
2090
|
+
let accentLetter = null;
|
|
2091
|
+
if (this.state.showAccentBar) accentLetter = this.fetchAccentLetter(null);
|
|
2092
|
+
|
|
2093
|
+
let toolbarExtraMargin = 0;
|
|
2094
|
+
if (this.props.className.includes("answer-edit-input")) {
|
|
2095
|
+
toolbarExtraMargin = 20;
|
|
2096
|
+
}
|
|
2097
|
+
console.log("Toolbar extra margin: " + toolbarExtraMargin);
|
|
2098
|
+
|
|
2099
|
+
return (
|
|
2100
|
+
<>
|
|
2101
|
+
<div className={useClassName}>
|
|
2102
|
+
<div className="MathRichInput-scrollview">
|
|
2103
|
+
{this.state.hasFocus && (
|
|
2104
|
+
<Toolbar
|
|
2105
|
+
className="Toolbar"
|
|
2106
|
+
enableMath={this.enableMath()}
|
|
2107
|
+
enableFontStyling={this.enableFontStyling()}
|
|
2108
|
+
style={this.translateToolbar}
|
|
2109
|
+
keyPressed={this.state.keyPressed}
|
|
2110
|
+
activeButtons={this.state.activeButtons}
|
|
2111
|
+
onButtonClick={this.handleToolbarButtonClick}
|
|
2112
|
+
fetchAccentLetter={this.fetchAccentLetter}
|
|
2113
|
+
insertAccentLetter={this.insertAccentLetter}
|
|
2114
|
+
extraMargin={toolbarExtraMargin}
|
|
2115
|
+
/>
|
|
2116
|
+
)}
|
|
2117
|
+
{this.state.showAccentBar && (
|
|
2118
|
+
<AccentBar
|
|
2119
|
+
compactMode={true}
|
|
2120
|
+
accentLetter={accentLetter}
|
|
2121
|
+
keyPressed={this.state.keyPressed}
|
|
2122
|
+
fetchAccentLetter={this.fetchAccentLetter}
|
|
2123
|
+
onButtonClick={(e, oldCharacter, newCharacter) =>
|
|
2124
|
+
this.handleAccentButtonClick(e, oldCharacter, newCharacter)
|
|
2125
|
+
}
|
|
2126
|
+
/>
|
|
2127
|
+
)}
|
|
2128
|
+
<span
|
|
2129
|
+
className="MathRichInput" // Don't change this name - used to various components to find node
|
|
2130
|
+
contentEditable="true"
|
|
2131
|
+
ref={(r) => (this.editableDiv = r)}
|
|
2132
|
+
onInput={this.handleInput}
|
|
2133
|
+
onMouseDown={this.handleMouseDown}
|
|
2134
|
+
onMouseUp={this.handleMouseUp}
|
|
2135
|
+
onFocus={this.handleFocus}
|
|
2136
|
+
onBlur={this.handleBlur}
|
|
2137
|
+
onCompositionStart={this.handleCompositionStart}
|
|
2138
|
+
onCompositionEnd={this.handleCompositionEnd}
|
|
2139
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
2140
|
+
data-placeholder={this.props.placeholder || ""}
|
|
2141
|
+
></span>
|
|
2142
|
+
</div>
|
|
2143
|
+
</div>
|
|
2144
|
+
{this.state.showEquationEditor && (
|
|
2145
|
+
<EquationEditorModal
|
|
2146
|
+
ref={(r) => (this.equationEditorModal = r)}
|
|
2147
|
+
handleClose={this.handleEquationEditorClose}
|
|
2148
|
+
handleInsert={this.handleEquationEditorInsert}
|
|
2149
|
+
latex={this.state.equationEditorLatex}
|
|
2150
|
+
/>
|
|
2151
|
+
)}
|
|
2152
|
+
</>
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2244
2155
|
}
|