@zzish/math-rich-input 0.1.42 → 0.1.44

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.
@@ -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
- 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,
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
- useExpertMode: false,
56
- selectedTab: null,
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
- a: "à",
62
- e: "è",
63
- i: "ì",
64
- o: "ò",
65
- u: "ù",
66
- d: "ð",
67
- A: "À",
68
- E: "È",
69
- I: "Ì",
70
- O: "Ò",
71
- U: "Ù",
72
- D: "Ð",
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
- 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: "Ý",
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
- a: "â",
92
- e: "ê",
93
- i: "î",
94
- o: "ô",
95
- u: "û",
96
- A: "Â",
97
- E: "Ê",
98
- I: "Î",
99
- O: "Ô",
100
- U: "Û",
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
- a: "ä",
105
- e: "ë",
106
- i: "ï",
107
- o: "ö",
108
- u: "ü",
109
- y: "ÿ",
110
- A: "Ä",
111
- E: "Ë",
112
- I: "Ï",
113
- O: "Ö",
114
- U: "Ü",
115
- Y: "Ÿ",
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
- a: "ā",
123
- e: "ē",
124
- i: "ī",
125
- o: "ō",
126
- u: "ū",
127
- A: "Ā",
128
- E: "Ē",
129
- I: "Ī",
130
- O: "Ō",
131
- U: "Ū",
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
- "`": 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,
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
- 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
- };
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
- // this.handleInput = this.handleInput.bind(this);
172
- // this.handleKeyDown = this.handleKeyDown.bind(this);
173
- // this.handleKeyUp = this.handleKeyUp.bind(this);
171
+ // this.handleInput = this.handleInput.bind(this);
172
+ // this.handleKeyDown = this.handleKeyDown.bind(this);
173
+ // this.handleKeyUp = this.handleKeyUp.bind(this);
174
174
 
175
- // Remember the last key pressed
176
- this.keyPressed = null;
177
- this.keyPressStartTime = -1;
175
+ // Remember the last key pressed
176
+ this.keyPressed = null;
177
+ this.keyPressStartTime = -1;
178
178
 
179
- // This gets updated in coomponent did mount, used to position the toolbar correctly
180
- this.translateToolbar = "";
179
+ // This gets updated in coomponent did mount, used to position the toolbar correctly
180
+ this.translateToolbar = "";
181
181
 
182
- // Whether the text contains HTML or Maths
183
- this.isHtml = false;
184
- this.hasMath = false;
182
+ // Whether the text contains HTML or Maths
183
+ this.isHtml = false;
184
+ this.hasMath = false;
185
185
 
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;
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
- if (hasMath) return MIME_TYPE_ZZISH_HTML_MATH;
276
- if (hasHtml || !outputPlainText) return MIME_TYPE_HTML;
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
- undo = () => {
346
- const currentState = this.undoHistory.pop();
200
+ this.lastSetActiveButtons = null;
347
201
 
348
- let previousState = this.undoHistory.pop();
349
- if (previousState === null || previousState === undefined) {
350
- previousState = this.initialHistoryState;
351
- }
202
+ this.onChangeCallback = this.props.onChange;
352
203
 
353
- if (this.redoHistory.length > 0) {
354
- let redoLast = this.redoHistory[this.redoHistory.length - 1];
355
- if (!this.deepEqual(redoLast, currentState))
356
- this.redoHistory.push(currentState);
357
- } else this.redoHistory.push(currentState);
358
-
359
- this.applyChangesToComponent(
360
- previousState.value,
361
- previousState.mimeType,
362
- previousState.rangeParams
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
- _findNodeWithIndex(index) {
406
- return findNodeWithIndex(this.editableDiv, index);
407
- }
408
-
409
- _findIndexOfNode(node) {
410
- return findIndexOfNode(this.editableDiv, node);
411
- }
412
-
413
- _findFirstTextNode() {
414
- return findFirstTextNode(this.editableDiv);
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
- // Handle special case of deleting last character when in bold etc text
438
- // in order to resolve a bug
439
- let deleteToEmpty = false;
440
- if (
441
- start_node_index === 0 &&
442
- start_offset === 1 &&
443
- end_node_index === 0 &&
444
- end_offset === 1
445
- ) {
446
- if (!isKatexNode(startNode) && getNodeTextLength(startNode) === 1)
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
- if (start_node_index === 0 && start_offset === 0 && end_node_index !== 0) {
450
- if (
451
- getNodeTextLength(endNode) === end_offset &&
452
- this._findNodeWithIndex(end_node_index + 1) === null
453
- )
454
- deleteToEmpty = true;
242
+
243
+ enableHtml() {
244
+ let outputTex = this.props.outputTex ?? false;
245
+ return !outputTex;
455
246
  }
456
247
 
457
- if (deleteToEmpty) {
458
- // console.log("deleteToEmpty")
459
- let new_rangeParams = {
460
- startNodeIndex: 0,
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
- // Treat backspace normally in the following situations
479
- if (start_offset > 1) return;
480
- if (start_node_index <= 0) return;
481
- if (
482
- start_node_index !== rangeParams.endNodeIndex ||
483
- start_offset !== rangeParams.endOffset
484
- )
485
- return;
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
- // At this point we must have start_offset === 0 or 1 and a previous node
263
+ getMimeType(hasHtml, hasMath) {
264
+ let outputTex = this.props.outputTex ?? false;
265
+ let outputPlainText = this.props.outputPlainText ?? false;
488
266
 
489
- let prevNode = this._findNodeWithIndex(start_node_index - 1);
267
+ if (outputTex) {
268
+ if (hasMath || !outputPlainText) return MIME_TYPE_TEX;
269
+ else return MIME_TYPE_PLAIN_TEXT;
270
+ }
490
271
 
491
- if (!isKatexNode(prevNode)) {
492
- if (this.nodeStartsWithSmallSpace(startNode)) {
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
- // The previous node is a katex node
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
- let new_rangeParams = {
534
- startNodeIndex: new_index,
535
- startOffset: new_offset,
536
- endNodeIndex: new_index,
537
- endOffset: new_offset,
278
+ _countNodeTypes = () => {
279
+ return countNodeTypes(this.editableDiv);
538
280
  };
539
281
 
540
- // OK, time to delete the katex node!
541
- node.parentNode.removeChild(node);
542
- let raw_text = elementToMarkedRawText(
543
- this.editableDiv,
544
- null,
545
- 0,
546
- this.enableHtml()
547
- );
548
- // console.log("After deleting katex: "+raw_text)
549
-
550
- let nodeCounts = this._countNodeTypes();
551
- let mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.math > 0);
552
- raw_text = removeEncodingIfPlainText(raw_text, mimeType, this.enableHtml());
553
-
554
- this.applyChangesToComponent(
555
- raw_text,
556
- mimeType,
557
- new_rangeParams,
558
- this.props.useExpertMode,
559
- this.props.selectedTab
560
- );
561
-
562
- // Consume the event
563
- e.preventDefault();
564
- };
565
-
566
- nodeStartsWithSmallSpace(node) {
567
- if (
568
- node.nodeValue !== null &&
569
- node.nodeValue !== undefined &&
570
- node.nodeValue.length > 0 &&
571
- node.nodeValue.charAt(0) === SMALL_SPACE
572
- )
573
- return true;
574
-
575
- return false;
576
- }
577
-
578
- inputStartsWithSmallSpace = () => {
579
- return startsWithSmallSpace(this.editableDiv);
580
- };
581
-
582
- handleKeyDownArrowLeft = (e) => {
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
- } else {
597
- new_rangeParams = {
598
- startGlobalOffset: new_globalOffset,
599
- endGlobalOffset: new_globalOffset,
600
- };
601
- }
602
- // console.log(new_rangeParams)
603
- e.preventDefault();
604
- this._setRangeParams(new_rangeParams);
605
- return;
606
- };
607
-
608
- handleKeyDownArrowRight = (e) => {
609
- // console.log("handleKeyDownArrowRight")
610
- let rangeParams = this._getRangeParams();
611
- let index = rangeParams.startNodeIndex;
612
- let offset = rangeParams.startOffset;
613
-
614
- let new_rangeParams = null
615
- let new_globalOffset = rangeParams.endGlobalOffset + 1;
616
- if (e.shiftKey && new_globalOffset > rangeParams.startGlobalOffset) {
617
- new_rangeParams = {
618
- startGlobalOffset: rangeParams.startGlobalOffset,
619
- endGlobalOffset: new_globalOffset,
620
- };
621
- } else {
622
- new_rangeParams = {
623
- startGlobalOffset: new_globalOffset,
624
- endGlobalOffset: new_globalOffset,
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
- e.preventDefault();
628
- this._setRangeParams(new_rangeParams);
629
- return;
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
- e.preventDefault();
732
- };
370
+ _getRangeParams(editableDiv = null) {
371
+ if (editableDiv === null) editableDiv = this.editableDiv;
372
+ let rangeParams = getRangeParams(editableDiv);
373
+ return rangeParams;
374
+ }
733
375
 
734
- showEquationEditorOnKeyDown = (e) => {
735
- this.showEquationEditor(true);
736
- e.preventDefault();
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
- handleKeyDownEnter = (e) => {
740
- try {
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
- // If a new accent has been set, store it in this.accent, otherwise clear this.accent
778
- if (new_accent === "") this.accent = "";
779
- else {
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
- if (new_char !== "") {
786
- // Prevent the original character being insereted
787
- e.preventDefault();
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
- handleKeyDown = (e) => {
828
- try {
829
- // Lets set up or update the undo redo history
830
- this.initialiseHistory();
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
- if ((e.key === "z" || e.key === "Z") && (e.ctrlKey || e.metaKey)) {
833
- if (e.shiftKey) this.redo();
834
- else this.undo();
835
- e.preventDefault();
836
- return;
837
- }
838
-
839
- if (!(e.ctrlKey || e.metaKey)) this.resetHistoryRedo();
840
-
841
- if (
842
- (e.metaKey || e.ctrlKey) &&
843
- (e.key === "b" ||
844
- e.key === "i" ||
845
- e.key === "u" ||
846
- e.key === "6" ||
847
- e.key === "^" ||
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
- /(Win)/i.test(window.navigator.platform) &&
861
- e.ctrlKey &&
862
- (e.key === "6" || e.key === "^")
423
+ start_node_index === 0 &&
424
+ start_offset === 1 &&
425
+ end_node_index === 0 &&
426
+ end_offset === 1
863
427
  ) {
864
- // do nothing
865
- } else {
866
- // otherwise
867
- if (e.key === "6" || e.key === "^" || e.key === "+" || e.key === "=")
868
- this.styleText(e, "Superscript");
869
- // Don't apply subscript if it is the browser command key for "smaller"
870
- // if (/(Win)/i.test(window.navigator.platform) && e.ctrlKey && e.key === "-")
871
- // return
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
- if ((e.metaKey || e.ctrlKey) && e.key === "v") {
883
- // console.log("Executing paste");
884
- document.execCommand("paste");
885
- return;
886
- }
887
- if ((e.metaKey || e.ctrlKey) && e.key === "c") {
888
- // console.log("Executing copy");
889
- document.execCommand("copy");
890
- return;
891
- }
892
- if ((e.metaKey || e.ctrlKey) && e.key === "x") {
893
- // console.log("Executing cut");
894
- document.execCommand("cut");
895
- return;
896
- }
897
-
898
- // Ignore automatic key repeat presses on Windows so that we can show an accent bar on
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
- if (e.altKey && e.key === "Alt") {
916
- let key = this.fetchAccentLetter();
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
- key === "a" ||
919
- key === "e" ||
920
- key === "i" ||
921
- key === "o" ||
922
- key === "u" ||
923
- key === "c" ||
924
- key === "l" ||
925
- key === "n" ||
926
- key === "s" ||
927
- key === "?" ||
928
- key === "!"
929
- ) {
930
- console.log("showing/hiding accent bar")
931
- this.setState({ showAccentBar: !this.state.showAccentBar});
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
- console.log("hiding accent bar")
934
- this.setState({ showAccentBar: false });
570
+ new_rangeParams = {
571
+ startGlobalOffset: new_globalOffset,
572
+ endGlobalOffset: new_globalOffset,
573
+ };
935
574
  }
936
- e.preventDefault()
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
- if (e.key === "Backspace") {
955
- this.handleKeyDownBackspace(e);
956
- return;
957
- }
958
- if (e.key === "ArrowLeft") {
959
- this.handleKeyDownArrowLeft(e);
960
- return;
961
- }
962
- if (e.key === "ArrowRight") {
963
- this.handleKeyDownArrowRight(e);
964
- return;
965
- }
966
- if (e.key === "$") {
967
- this.handleKeyDownDollar(e);
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
- // Implement special command + keys
972
- if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
973
- this.showEquationEditorOnKeyDown(e);
974
- return;
975
- } else if (/(Mac)/i.test(window.navigator.platform)) {
976
- if (e.ctrlKey && e.key === "=") {
977
- // CTRL+= inserts equation in Word on Mac
978
- this.showEquationEditorOnKeyDown(e);
979
- return;
980
- }
981
- } else if (/(Win)/i.test(window.navigator.platform)) {
982
- if (e.altKey && e.key === "=") {
983
- // ALT+= inserts equation in Word on PC
984
- this.showEquationEditorOnKeyDown(e);
985
- return;
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
- // Lets handle the enter key (new line) ourselves
990
- if (e.key === "Enter") {
991
- this.handleKeyDownEnter(e);
992
- return;
993
- }
689
+ e.preventDefault();
690
+ };
994
691
 
995
- // Show the accent bar on long key presses
996
- if (/(Win)/i.test(window.navigator.platform)) {
997
- let key = e.key.toLowerCase();
998
- if (
999
- key === "a" ||
1000
- key === "e" ||
1001
- key === "i" ||
1002
- key === "o" ||
1003
- key === "u" ||
1004
- key === "c" ||
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
- // Stop editing if within rendered katex node, if somehow the cursor ends up there (it shouldn't)
1021
- let params = this._getRangeParams();
1022
- let startNode = this._findNodeWithIndex(params.startNodeIndex);
1023
- let endNode = this._findNodeWithIndex(params.endNodeIndex);
1024
- if (!this.editableDivIsPlainText()) {
1025
- if (params.startNodeIndex === -1) {
1026
- console.error("Couldn't get range params");
1027
- return;
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
- if (isKatexNode(startNode) || isKatexNode(endNode)) {
1030
- console.error("User trying to edit inside latex node");
1031
- e.preventDefault();
1032
- return;
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
- if (this.handleKeyDownAccentHelper(e))
1037
- return;
785
+ handleKeyDown = (e) => {
786
+ try {
787
+ // Lets set up or update the undo redo history
788
+ this.initialiseHistory();
1038
789
 
1039
- // Check if inserting character in element that starts with small space
1040
- if (e.key.length === 1 && // ie, it's not something like "arrowLeft"
1041
- params.startOffset === 0 &&
1042
- this.nodeStartsWithSmallSpace(startNode)) {
1043
- // console.log("Handling character input at start of text node with small space")
1044
- this.insertAfterSmallSpace(e.key, params);
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
- handleKeyUp = (e) => {
1062
- try {
1063
- this.keyPressed = e.key;
1064
- this.keyPressStartTime = -1;
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
- // If the cursor is in a katex node, then move it to the next node (unless there is no
1067
- // net node in which case move it to the previous node)
1068
- if (e.key === "ArrowRight") {
1069
- let rangeParams = this._getRangeParams();
1070
- let index = rangeParams.startNodeIndex;
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
- if (index >= 0) {
1073
- if (isKatexNode(this._findNodeWithIndex(index))) {
1074
- let new_index = 0;
1075
- let new_offset = 0;
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
- let nextNode = this._findNodeWithIndex(index + 1);
1078
- if (nextNode === null) {
1079
- new_index = index + 1;
1080
- new_offset = SMALL_SPACE_LENGTH; // Nominally should be 0, but 1 due to inserted SMALL_SPACE
1081
- } else {
1082
- new_index = index - 1;
1083
- new_offset = getNodeTextLength(
1084
- this._findNodeWithIndex(new_index)
1085
- );
1086
- if (new_offset < 0) {
1087
- console.error("Failed to get node text length");
1088
- new_offset = 0;
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
- let new_rangeParams = {
1093
- startNodeIndex: new_index,
1094
- startOffset: new_offset,
1095
- endNodeIndex: new_index,
1096
- endOffset: new_offset,
1097
- };
1098
- this._setRangeParams(new_rangeParams);
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
- if (e.key === "ArrowLeft") {
1104
- let rangeParams = this._getRangeParams();
1105
- let index = rangeParams.startNodeIndex;
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
- // If the cursor is in a katex node, then move it to the previous node (unless there is no
1108
- // previous node in which case move it to the next node)
1109
- if (index >= 0) {
1110
- if (isKatexNode(this._findNodeWithIndex(index))) {
1111
- let new_index = 0;
1112
- let new_offset = 0;
1113
-
1114
- if (index - 1 >= 0) {
1115
- new_index = index - 1;
1116
- new_offset = getNodeTextLength(
1117
- this._findNodeWithIndex(new_index)
1118
- );
1119
- if (new_offset < 0) {
1120
- console.error("Failed to get node text length");
1121
- new_offset = 0;
1122
- }
1123
- } else {
1124
- new_index = index + 1;
1125
- new_offset = SMALL_SPACE_LENGTH;
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
- let new_rangeParams = {
1129
- startNodeIndex: new_index,
1130
- startOffset: new_offset,
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
- this.updateActiveButtons();
1141
- } catch (error) {
1142
- console.error(error);
1143
- }
1144
- };
1145
-
1146
- handleInput = (event) => {
1147
- try {
1148
- // console.log("handleInput")
1149
- if (this.isComposing) return;
1150
-
1151
- let raw_text = elementToMarkedRawText(
1152
- this.editableDiv,
1153
- null,
1154
- 0,
1155
- this.enableHtml()
1156
- );
1157
- const params = this._getRangeParams();
1158
- if (params === null || params === undefined)
1159
- console.error("Could not get range params from input field");
1160
- else if (
1161
- params.startNodeIndex === null ||
1162
- params.startNodeIndex === undefined
1163
- )
1164
- console.error(
1165
- "Could not get range params from input field, startNodeIndex===" +
1166
- params.startNodeIndex
1167
- );
1168
- else {
1169
- // console.log("Current range params:")
1170
- // console.log(params)
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
- handlePaste = (event) => {
1256
- let clipboardData = event.clipboardData || window.clipboardData;
1257
- if (clipboardData === null || clipboardData === undefined) {
1258
- console.warn("handlePaste called, but no clipboard data");
1259
- return;
1260
- }
1261
- // for (let i = 0; i < clipboardData.types.length; i++)
1262
- // console.log(
1263
- // clipboardData.types[i] +
1264
- // ": " +
1265
- // clipboardData.getData(clipboardData.types[i])
1266
- // );
1267
- let pasteHtml = clipboardData.getData("text/html");
1268
- if (pasteHtml !== null && pasteHtml.length > 0) {
1269
- // console.log("** Paste html");
1270
- // console.log(pasteHtml);
1271
- pasteHtml = sanitiseHtml(pasteHtml);
1272
- // console.log("** Santised paste html");
1273
- // console.log(pasteHtml);
1274
- this.insertHtml(pasteHtml);
1275
- event.preventDefault();
1276
- } else {
1277
- let paste = clipboardData.getData("text");
1278
- const selection = window.getSelection();
1279
- if (!selection.rangeCount) {
1280
- console.warn("handlePaste called, but unable to get selection");
1281
- return false;
1282
- }
1283
- event.preventDefault();
1284
- selection.deleteFromDocument();
1285
- this.insertRawText(paste);
1286
- }
1287
- };
1288
-
1289
- componentDidMount() {
1290
- try {
1291
- setupKatex();
1292
-
1293
- this.editableDiv.addEventListener("keydown", this.handleKeyDown);
1294
- this.editableDiv.addEventListener("keyup", this.handleKeyUp);
1295
- this.editableDiv.addEventListener("paste", this.handlePaste);
1296
- if (this.props.autofocus === true) this.editableDiv.focus();
1297
- } catch (error) {
1298
- console.error(error);
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
- componentWillUnmount() {
1303
- try {
1304
- this.editableDiv.removeEventListener("keydown", this.handleKeyDown);
1305
- this.editableDiv.removeEventListener("keyup", this.handleKeyUp);
1306
- this.editableDiv.removeEventListener("paste", this.handlePaste);
1307
- } catch (error) {
1308
- console.error(error);
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
- componentDidUpdate() {
1313
- try {
1314
- // console.log("componentDidUpdate")
1315
- if (
1316
- this.afterComponentUpdateData !== null &&
1317
- this.afterComponentUpdateData !== undefined &&
1318
- this.afterComponentUpdateData.value === this.props.value
1319
- ) {
1320
- this._setRangeParams(this.afterComponentUpdateData.rangeParams);
1321
- this.afterComponentUpdateData = null;
1322
- this.updateActiveButtons();
1323
- }
1324
- } catch (error) {
1325
- console.error(error);
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
- let latex = "";
1351
- let offset = 0;
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
- if (node === null)
1354
- node = this._findNodeWithIndex(rangeParams.startNodeIndex);
1291
+ let latex = "";
1292
+ let offset = 0;
1355
1293
 
1356
- // console.log(rangeParams)
1357
- // console.log(this.editableDiv.childNodes)
1358
- // console.log(node)
1294
+ if (node === null) node = this._findNodeWithIndex(rangeParams.startNodeIndex);
1359
1295
 
1360
- if (node === null || node === undefined) {
1361
- console.error(
1362
- "Could not find node with index: " + rangeParams.startNodeIndex
1363
- );
1364
- // console.log("Child nodes:");
1365
- // console.log(this.editableDiv.childNodes);
1366
- node = this._findFirstTextNode();
1367
- offset = 0;
1368
- }
1369
-
1370
- if (isKatexNode(node)) {
1371
- latex = getLatex(node);
1372
- offset = 0;
1373
- this.editingExistingEquation = true;
1374
- this.editingNode = node;
1375
- } else {
1376
- latex = "";
1377
- offset = rangeParams.startOffset;
1378
- this.editingExistingEquation = false;
1379
- this.editingNode = null;
1380
- }
1381
-
1382
- this.markedRawText = elementToMarkedRawText(
1383
- this.editableDiv,
1384
- node,
1385
- offset,
1386
- this.enableHtml()
1387
- );
1388
- // console.log("Marked text:"+this.markedRawText)
1389
- this.moveCursorOnInsert = moveCursorOnInsert;
1390
- this.setState({
1391
- showEquationEditor: true,
1392
- equationEditorLatex: latex,
1393
- });
1394
- } catch (error) {
1395
- console.error(error);
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
- node = node.parentNode;
1411
- } while (node !== this.editableDiv && node !== null);
1335
+ };
1412
1336
 
1413
- if (!isLatex) {
1414
- return;
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
- this.showEquationEditorOnMouseUp = true;
1418
- // Save current range parameters so that we can restore them later if we wish
1419
- this.setOldRangeParams(this._getRangeParams());
1351
+ if (!isLatex) {
1352
+ return;
1353
+ }
1420
1354
 
1421
- // this.editableDiv.focus()
1422
- // this.showEquationEditor(false, node)
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
- // event.preventDefault()
1425
- } catch (error) {
1426
- console.error(error);
1427
- console.log(node);
1428
- }
1429
- };
1359
+ // this.editableDiv.focus()
1360
+ // this.showEquationEditor(false, node)
1430
1361
 
1431
- handleMouseUp = (event) => {
1432
- try {
1433
- this.initialiseHistory();
1362
+ // event.preventDefault()
1363
+ } catch (error) {
1364
+ console.error(error);
1365
+ console.log(node);
1366
+ }
1367
+ };
1434
1368
 
1435
- let params = this._getRangeParams();
1436
- let startNode = this._findNodeWithIndex(params.startNodeIndex);
1437
- let endNode = this._findNodeWithIndex(params.endNodeIndex);
1369
+ handleMouseUp = (event) => {
1370
+ try {
1371
+ this.initialiseHistory();
1438
1372
 
1439
- if (startNode === null || endNode === null) {
1440
- console.warn("Start or end node was null");
1441
- return;
1442
- }
1373
+ let params = this._getRangeParams();
1374
+ let startNode = this._findNodeWithIndex(params.startNodeIndex);
1375
+ let endNode = this._findNodeWithIndex(params.endNodeIndex);
1443
1376
 
1444
- if (
1445
- params.startNodeIndex === params.endNodeIndex &&
1446
- params.startOffset === params.endOffset &&
1447
- this.showEquationEditorOnMouseUp
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
- // If our range starts or ends inside a katex node, move it out
1455
- let new_rangeParams = null;
1456
- if (isKatexNode(startNode)) {
1457
- if (new_rangeParams === null) new_rangeParams = { ...params };
1458
- if (
1459
- params.startNodeIndex === params.endNodeIndex &&
1460
- params.startOffset === params.endOffset
1461
- ) {
1462
- new_rangeParams.startNodeIndex = params.startNodeIndex + 1;
1463
- new_rangeParams.startOffset = SMALL_SPACE_LENGTH;
1464
- } else {
1465
- new_rangeParams.startNodeIndex = params.startNodeIndex - 1;
1466
- let prev_node = this._findNodeWithIndex(params.startNodeIndex - 1);
1467
- new_rangeParams.startOffset = getNodeTextLength(prev_node);
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
- this.updateActiveButtons();
1483
- } catch (error) {
1484
- console.error(error);
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
- handleEquationEditorClose = () => {
1489
- try {
1490
- this.setState({ showEquationEditor: false }, () => {
1491
- this.editableDiv.focus();
1492
- // this._setRangeParams(getOldRangeParams)
1493
- });
1494
- } catch (error) {
1495
- console.error(error);
1496
- }
1497
- };
1498
-
1499
- handleEquationEditorInsert = (latex) => {
1500
- try {
1501
- // console.log("Inserting latex: "+latex)
1502
-
1503
- let start_pos = this.markedRawText.indexOf(MARK);
1504
- let end_pos = this.markedRawText.indexOf(MARK, start_pos + MARK.length);
1505
- let oldRangeParams = this.getOldRangeParams();
1506
- // console.log("Old range params")
1507
- // console.log(oldRangeParams)
1508
-
1509
- // Handle case of empty string returned
1510
- if (latex === "") {
1511
- // console.log("Empty latex returned from equation editor")
1512
- let new_raw_text =
1513
- this.markedRawText.substring(0, start_pos) +
1514
- this.markedRawText.substring(end_pos + MARK.length);
1515
-
1516
- // Delete node if it was a latex node
1517
- let new_index = -1;
1518
- let new_offset = -1;
1519
- if (this.editingExistingEquation) {
1520
- let indexOfEditedNode = this._findIndexOfNode(this.editingNode);
1521
-
1522
- if (oldRangeParams.startNodeIndex === indexOfEditedNode + 1) {
1523
- new_index = oldRangeParams.startNodeIndex - 2;
1524
- new_offset = getNodeTextLength(this._findNodeWithIndex(new_index));
1525
- } else if (oldRangeParams.startNodeIndex > indexOfEditedNode + 1) {
1526
- new_index = oldRangeParams.startNodeIndex - 2;
1527
- new_offset = oldRangeParams.startOffset;
1528
- } else {
1529
- new_index = oldRangeParams.startNodeIndex;
1530
- new_offset = oldRangeParams.startOffset;
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
- let new_rangeParams = {
1535
- startNodeIndex: new_index,
1536
- startOffset: new_offset,
1537
- endNodeIndex: new_index,
1538
- endOffset: new_offset,
1539
- };
1540
- this.setOldRangeParams(new_rangeParams);
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
- let nodeCounts = this._countNodeTypes();
1543
- let mimeType = null;
1544
- if (this.editingExistingEquation)
1545
- mimeType = this.getMimeType(
1546
- nodeCounts.html > 0,
1547
- nodeCounts.katex > 1
1548
- );
1549
- else
1550
- mimeType = this.getMimeType(
1551
- nodeCounts.html > 0,
1552
- nodeCounts.katex > 0
1553
- );
1554
- new_raw_text = removeEncodingIfPlainText(
1555
- new_raw_text,
1556
- mimeType,
1557
- this.enableHtml()
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
- this.applyChangesToComponent(
1561
- new_raw_text,
1562
- mimeType,
1563
- new_rangeParams,
1564
- this.props.useExpertMode,
1565
- this.props.selectedTab
1566
- );
1567
- this.setState({ showEquationEditor: false });
1568
- return;
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
- success = document.execCommand("removeFormat", false, null);
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
- handleToolbarButtonClick = (event, buttonName) => {
1678
- try {
1679
- if (buttonName === "Equation") {
1680
- this.showEquationEditor(true);
1681
- }
1682
- let style = buttonName;
1683
- let success = this.styleText(event, style);
1684
- if (!success) console.warn("execCommand returned false: " + style);
1685
- } catch (error) {
1686
- console.error(error);
1687
- }
1688
- };
1689
-
1690
- fetchAccentLetter = (event) => {
1691
- try {
1692
- let rangeParams = this._getRangeParams();
1693
- if (rangeParams === null) {
1694
- console.error("Cound not get range params");
1695
- return null;
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
- var firstNode = frag.firstChild;
1791
- range.insertNode(frag);
1792
- // Preserve the selection
1793
- if (lastNode) {
1794
- range = range.cloneRange();
1795
- range.setStartAfter(lastNode);
1796
- if (selectPastedContent) {
1797
- range.setStartBefore(firstNode);
1798
- } else {
1799
- range.collapse(true);
1800
- }
1801
- sel.removeAllRanges();
1802
- sel.addRange(range);
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
- } else if ((sel = document.selection) && sel.type != "Control") {
1806
- // IE < 9
1807
- var originalRange = sel.createRange();
1808
- originalRange.collapse(true);
1809
- sel.createRange().pasteHTML(html);
1810
- if (selectPastedContent) {
1811
- range = sel.createRange();
1812
- range.setEndPoint("StartToStart", originalRange);
1813
- range.select();
1814
- }
1815
- }
1816
- };
1817
-
1818
- insertHtml = (new_html) => {
1819
- try {
1820
- // console.log("New html: "+new_html)
1821
- // console.log("inserting: "+new_html)
1822
- this.pasteHtmlAtCaret(new_html, true);
1823
- let new_rangeParams = null;
1824
- // console.log("New range params: ")
1825
- try {
1826
- new_rangeParams = this._getRangeParams();
1827
- // console.log("New range params: ")
1828
- // console.log(new_rangeParams)
1829
- } catch (error) {
1830
- console.error(error);
1831
- }
1832
- // TODO: Fix the bug that fails to get range params correctly
1833
- if (
1834
- new_rangeParams.startNodeIndex < 0 ||
1835
- new_rangeParams.endNodeIndex < 0
1836
- ) {
1837
- console.warn(
1838
- "Selection range params were not fetched correctly. Unable to set highlight inserted text."
1839
- );
1840
- }
1841
- // Even if fetches valid range params, they are probably incorrect
1842
- // So lets clear them for now
1843
- new_rangeParams = {
1844
- startNodeIndex: 0,
1845
- startOffset: 0,
1846
- endNodeIndex: 0,
1847
- endOffset: 0,
1848
- };
1849
- let new_raw_text = elementToMarkedRawText(
1850
- this.editableDiv,
1851
- null,
1852
- -1,
1853
- this.enableHtml()
1854
- );
1855
- // console.log("New raw text:"+new_raw_text)
1856
- let nodeCounts = this._countNodeTypes();
1857
- let mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.math > 0);
1858
- new_raw_text = removeEncodingIfPlainText(
1859
- new_raw_text,
1860
- mimeType,
1861
- this.enableHtml()
1862
- );
1863
- // OK, got to remove the double <p> and double </p> inserted
1864
- // After inserting <p>a</p><p>b</p> into <p>xy</p> we will have
1865
- // <P>x<P>a</P><P>b</P>y</P> and we want <P>xa</P><P>by</P>
1866
- try {
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
- } while (i >= 0 && i < new_raw_text.length - 4);
1883
- i = -1;
1884
- do {
1885
- i = new_raw_text.indexOf("</P>", i + 1);
1886
- if (i >= 0) {
1887
- let j = new_raw_text.indexOf("</P>", i + 1);
1888
- let k = new_raw_text.indexOf("<P>", i + 1);
1889
- if (j >= 0) {
1890
- if (k < 0 || k > j) {
1891
- new_raw_text =
1892
- new_raw_text.substring(0, i) +
1893
- new_raw_text.substring(i + 4, new_raw_text.length);
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
- } while (i >= 0 && i < new_raw_text.length - 4);
1899
- } catch (error) {
1900
- console.error(error);
1901
- }
1902
- // console.log("New raw text:"+new_raw_text)
1903
- this.applyChangesToComponent(
1904
- new_raw_text,
1905
- mimeType,
1906
- new_rangeParams,
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
- let new_raw_text = removeMarks(
2044
- insertCharacterBeforeMarks(raw_text, new_char))
2045
-
2046
- let new_rangeParams = {
2047
- startNodeIndex:rangeParams.startNodeIndex,
2048
- startNodeOffset:1+new_char.length,
2049
- endNodeIndex:rangeParams.startNodeIndex,
2050
- endNodeOffset:1+new_char.length,
2051
- startGlobalOffset:rangeParams.startGlobalOffset+new_char.length,
2052
- endGlobalOffset:rangeParams.endGlobalOffset+new_char.length,
2053
- }
2054
-
2055
- new_raw_text = removeEncodingIfPlainText(
2056
- new_raw_text,
2057
- this.props.mimeType,
2058
- this.enableHtml()
2059
- );
2060
-
2061
- this.applyChangesToComponent(
2062
- new_raw_text,
2063
- this.props.mimeType,
2064
- new_rangeParams,
2065
- this.props.useExpertMode,
2066
- this.props.selectedTab
2067
- );
2068
- } catch (error) {
2069
- console.error(error);
2070
- }
2071
- };
2072
-
2073
- insertAccentLetter = (event, old_char, new_char) => {
2074
- try {
2075
- let rangeParams = this._getRangeParams();
2076
- let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
2077
- let offset = rangeParams.startOffset;
2078
-
2079
- let raw_text = elementToMarkedRawText(
2080
- this.editableDiv,
2081
- node,
2082
- offset,
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
- } else { // replace old char
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
- focus = () => {
2133
- this.editableDiv.focus();
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
- handleFocus = (event) => {
2137
- const { onFocus } = this.props;
2138
- if (onFocus != null) {
2139
- onFocus();
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
- handleBlur = (event) => {
2150
- const { onBlur } = this.props;
2151
- if (onBlur != null) {
2152
- onBlur();
2153
- }
2154
- try {
2155
- console.log("Hiding accent bar 4")
2156
- this.setState({ hasFocus: false, showAccentBar: false });
2157
- } catch (error) {
2158
- console.error(error);
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
- handleCompositionStart = (event) => {
2163
- this.isComposing = true ;
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
- handleCompositionEnd = (event) => {
2167
- this.isComposing = false;
2168
- }
1951
+ let new_raw_text = removeMarks(insertCharacterBeforeMarks(raw_text, new_char));
2169
1952
 
2170
- render() {
2171
- if (this.props.value == null) {
2172
- console.error('MathRichInput must have prop called "value"');
2173
- console.log(this.props);
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
- const html = rawTextToHtml(
2177
- katex,
2178
- this.props.value || "",
2179
- this.props.mimeType
2180
- );
2181
-
2182
- let useClassName = "MathRichInput-Container"; // Don't change this name - used by various components to find node
2183
- if (this.props.className)
2184
- useClassName = this.props.className + " " + useClassName;
2185
-
2186
- let accentLetter = null;
2187
- if (this.state.showAccentBar) accentLetter = this.fetchAccentLetter(null);
2188
-
2189
- return (
2190
- <>
2191
- <div className={useClassName}>
2192
- <div className="MathRichInput-scrollview">
2193
- {this.state.hasFocus && (
2194
- <Toolbar
2195
- className="Toolbar"
2196
- enableMath={this.enableMath()}
2197
- enableFontStyling={this.enableFontStyling()}
2198
- style={this.translateToolbar}
2199
- keyPressed={this.state.keyPressed}
2200
- activeButtons={this.state.activeButtons}
2201
- onButtonClick={this.handleToolbarButtonClick}
2202
- fetchAccentLetter={this.fetchAccentLetter}
2203
- insertAccentLetter={this.insertAccentLetter}
2204
- />
2205
- )}
2206
- {this.state.showAccentBar && (
2207
- <AccentBar
2208
- compactMode={true}
2209
- accentLetter={accentLetter}
2210
- keyPressed={this.state.keyPressed}
2211
- fetchAccentLetter={this.fetchAccentLetter}
2212
- onButtonClick={(e, oldCharacter, newCharacter) =>
2213
- this.handleAccentButtonClick(e, oldCharacter, newCharacter)
2214
- }
2215
- />
2216
- )}
2217
- <span
2218
- className="MathRichInput" // Don't change this name - used to various components to find node
2219
- contentEditable="true"
2220
- ref={(r) => (this.editableDiv = r)}
2221
- onInput={this.handleInput}
2222
- onMouseDown={this.handleMouseDown}
2223
- onMouseUp={this.handleMouseUp}
2224
- onFocus={this.handleFocus}
2225
- onBlur={this.handleBlur}
2226
- onCompositionStart={this.handleCompositionStart}
2227
- onCompositionEnd={this.handleCompositionEnd}
2228
- dangerouslySetInnerHTML={{ __html: html }}
2229
- data-placeholder={this.props.placeholder || ""}
2230
- ></span>
2231
- </div>
2232
- </div>
2233
- {this.state.showEquationEditor && (
2234
- <EquationEditorModal
2235
- ref={(r) => (this.equationEditorModal = r)}
2236
- handleClose={this.handleEquationEditorClose}
2237
- handleInsert={this.handleEquationEditorInsert}
2238
- latex={this.state.equationEditorLatex}
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
  }