@zzish/math-rich-input 0.1.24 → 0.1.26

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.
@@ -7,6 +7,7 @@ import EquationEditorModal from "./EquationEditorModal";
7
7
  import Toolbar from "./Toolbar";
8
8
 
9
9
  import {
10
+ getMark,
10
11
  isKatexNode,
11
12
  elementToMarkedRawText,
12
13
  removeEncodingIfPlainText,
@@ -26,6 +27,7 @@ import {
26
27
  insertCharacterBeforeMarks,
27
28
  removeMarks,
28
29
  replaceMarksWithMath,
30
+ sanitiseHtml,
29
31
  } from "./mathRichInputHelper";
30
32
 
31
33
  import "./MathRichInput.css";
@@ -41,7 +43,7 @@ const SMALL_SPACE = String.fromCharCode(SMALL_SPACE_CHAR_CODE);
41
43
  const SMALL_SPACE_LENGTH = SMALL_SPACE.length;
42
44
  // const SMALL_SPACE_REG_EXP = new RegExp(SMALL_SPACE, "g")
43
45
 
44
- const MARK = "_M_A_R_K_";
46
+ const MARK = getMark();
45
47
  const MARK_REG_EXP = new RegExp(MARK, "g");
46
48
 
47
49
  // const LATEX_MARKER_START = "<annotation encoding=\"application/x-tex\">"
@@ -54,6 +56,101 @@ const DEFAULT_OPTIONS = {
54
56
  selectedTab: null,
55
57
  };
56
58
 
59
+ // Consts for creating accented letters in handleKeyDownAccentHelper
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: "Ð",
73
+ };
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: "Ý",
89
+ };
90
+ const CARET_LETTERS = {
91
+ a: "â",
92
+ e: "ê",
93
+ i: "î",
94
+ o: "ô",
95
+ u: "û",
96
+ A: "Â",
97
+ E: "Ê",
98
+ I: "Î",
99
+ O: "Ô",
100
+ U: "Û",
101
+ };
102
+ const TILDE_LETTERS = { a: "ã", n: "ñ", o: "õ", A: "Ã", N: "Ñ", O: "Õ" };
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: "Ÿ",
116
+ };
117
+ const AT_LETTERS = { a: "å", A: "Å" };
118
+ const AND_LETTERS = { a: "æ", o: "œ", s: "ß", A: "Æ", O: "Œ", S: "ß" };
119
+ const COMMA_LETTERS = { c: "ç", C: "Ç" };
120
+ const SLASH_LETTERS = { o: "ø", O: "Ø" };
121
+ const MINUS_LETTERS = {
122
+ a: "ā",
123
+ e: "ē",
124
+ i: "ī",
125
+ o: "ō",
126
+ u: "ū",
127
+ A: "Ā",
128
+ E: "Ē",
129
+ I: "Ī",
130
+ O: "Ō",
131
+ U: "Ū",
132
+ };
133
+ const CTRL_ALT_SHIFT_LETTERS = { "?": "¿", "!": "¡", "/": "¿", 1: "¡" }; // Necessary
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,
149
+ };
150
+ const ACCENT_KEYS = ["`", "'", "^", "~", ":", "@", "&", ",", "/", "-"];
151
+ const CTRL_ALT_SHIFT_KEYS = ["?", "!"];
152
+ // End of consts for creating accented letters in handleKeyDownAccentHelper
153
+
57
154
  export default class MathRichInput extends React.Component {
58
155
  constructor(props) {
59
156
  super(props);
@@ -110,6 +207,7 @@ export default class MathRichInput extends React.Component {
110
207
  this.initialHistoryState = null;
111
208
  this.undoHistory = [];
112
209
  this.redoHistory = [];
210
+ this.accent = "";
113
211
  }
114
212
 
115
213
  enableFontStyling() {
@@ -368,10 +466,12 @@ export default class MathRichInput extends React.Component {
368
466
  startOffset: 0,
369
467
  endNodeIndex: 0,
370
468
  endOffset: 0,
469
+ startGlobalOffset: 0,
470
+ endGlobalOffset: 0,
371
471
  };
372
472
  this.applyChangesToComponent(
373
473
  "",
374
- MIME_TYPE_PLAIN_TEXT,
474
+ this.getMimeType(false, false),
375
475
  new_rangeParams,
376
476
  this.props.useExpertMode,
377
477
  this.props.selectedTab
@@ -488,6 +588,15 @@ export default class MathRichInput extends React.Component {
488
588
  let index = rangeParams.startNodeIndex;
489
589
  let offset = rangeParams.startOffset;
490
590
 
591
+ let new_globalOffset = rangeParams.startGlobalOffset - 1;
592
+ let new_rangeParams = {
593
+ startGlobalOffset: new_globalOffset,
594
+ endGlobalOffset: new_globalOffset,
595
+ };
596
+ e.preventDefault();
597
+ this._setRangeParams(new_rangeParams);
598
+ return;
599
+
491
600
  // console.log("Old: "+index+"->"+offset)
492
601
 
493
602
  // The user shouldn't be in a katex node, but lets deal with it anyway
@@ -503,6 +612,8 @@ export default class MathRichInput extends React.Component {
503
612
  startOffset: new_offset,
504
613
  endNodeIndex: new_index,
505
614
  endOffset: new_offset,
615
+ startGlobalOffset: new_globalOffset,
616
+ endGlobalOffset: new_globalOffset,
506
617
  };
507
618
  this._setRangeParams(new_rangeParams);
508
619
  e.preventDefault();
@@ -567,16 +678,18 @@ export default class MathRichInput extends React.Component {
567
678
  }
568
679
 
569
680
  if (new_index === -1 || new_offset === -1) return;
570
-
571
- let new_rangeParams = {
572
- startNodeIndex: new_index,
573
- startOffset: new_offset,
574
- endNodeIndex: new_index,
575
- endOffset: new_offset,
576
- };
577
- this._setRangeParams(new_rangeParams);
578
-
579
- e.preventDefault();
681
+ else {
682
+ let new_rangeParams = {
683
+ startNodeIndex: new_index,
684
+ startOffset: new_offset,
685
+ endNodeIndex: new_index,
686
+ endOffset: new_offset,
687
+ startGlobalOffset: new_globalOffset,
688
+ endGlobalOffset: new_globalOffset,
689
+ };
690
+ this._setRangeParams(new_rangeParams);
691
+ e.preventDefault();
692
+ }
580
693
  };
581
694
 
582
695
  handleKeyDownArrowRight = (e) => {
@@ -585,6 +698,15 @@ export default class MathRichInput extends React.Component {
585
698
  let index = rangeParams.startNodeIndex;
586
699
  let offset = rangeParams.startOffset;
587
700
 
701
+ let new_globalOffset = rangeParams.startGlobalOffset + 1;
702
+ let new_rangeParams = {
703
+ startGlobalOffset: new_globalOffset,
704
+ endGlobalOffset: new_globalOffset,
705
+ };
706
+ e.preventDefault();
707
+ this._setRangeParams(new_rangeParams);
708
+ return;
709
+
588
710
  if (index < 0)
589
711
  // Empty input field
590
712
  return;
@@ -720,20 +842,32 @@ export default class MathRichInput extends React.Component {
720
842
  }
721
843
  if (!convert_to_latex) return;
722
844
 
845
+ // So we will convert to latex
846
+ // If we get the raw text without replacing the dollars with marks, the dollars
847
+ // will be converted to html codes. So we will replace them with marks and then
848
+ // replace the marks with dollars later
849
+ // Remove first dollar and insert marks into the node instead of dollars
850
+ let latex = node.nodeValue.substring(
851
+ first_dollar_pos + 1,
852
+ second_dollar_pos
853
+ );
854
+
723
855
  node.nodeValue =
724
856
  node.nodeValue.substring(0, first_dollar_pos) +
725
857
  MARK +
726
- node.nodeValue.substring(first_dollar_pos + 1, second_dollar_pos) +
858
+ latex +
727
859
  MARK +
728
860
  node.nodeValue.substring(second_dollar_pos);
729
861
 
862
+ // Get the raw text and now put the dollars back
730
863
  let raw_text = elementToMarkedRawText(
731
864
  e.target,
732
865
  null,
733
866
  0,
734
867
  this.enableHtml()
735
868
  );
736
- raw_text = raw_text.replace(MARK_REG_EXP, "$");
869
+ // now replace the marks with math
870
+ raw_text = replaceMarksWithMath(raw_text, latex, this.enableHtml());
737
871
 
738
872
  // We are turning one node into three and want the cursor at the start of the third node
739
873
  let new_rangeParams = {
@@ -779,6 +913,85 @@ export default class MathRichInput extends React.Component {
779
913
  e.preventDefault();
780
914
  };
781
915
 
916
+ handleKeyDownAccentHelper = (e) => {
917
+ // console.log(e.ctrlKey)
918
+ // console.log(e.altKey)
919
+ // console.log(e.shiftKey)
920
+ let new_accent = "";
921
+ let new_char = "";
922
+ // console.log(e.key)
923
+ if (e.ctrlKey) {
924
+ // First check for CTRL+ALT+SHIFT keys
925
+ if (e.shiftKey && e.altKey) {
926
+ console.log(e.key);
927
+ if (CTRL_ALT_SHIFT_LETTERS.hasOwnProperty(e.key))
928
+ new_char = CTRL_ALT_SHIFT_LETTERS[e.key];
929
+ }
930
+ // Second check special case of CTRL-tilde
931
+ else if (e.shiftKey && e.key === "`") new_accent = "~";
932
+ // Finally check for normal accent keys
933
+ else if (LETTERS.hasOwnProperty(e.key)) new_accent = e.key;
934
+ } else if (this.accent !== "") {
935
+ // Ignore key events for shift and caps lock as that can mean the user
936
+ // is simply changing to or from capitals and we don't want to reset
937
+ // this.accent in this case
938
+ if (e.key === "Shift") return false;
939
+ if (e.key === "CapsLock") return false;
940
+ // Get the possible letters that use this accent and test to see if the
941
+ // current key pressed is one of those letters.
942
+ let letters = LETTERS[this.accent];
943
+ if (letters.hasOwnProperty(e.key)) new_char = letters[e.key];
944
+ }
945
+ // If a new accent has been set, store it in this.accent, otherwise clear this.accent
946
+ if (new_accent === "") this.accent = "";
947
+ else {
948
+ e.preventDefault();
949
+ this.accent = new_accent;
950
+ console.log("Accent set: " + this.accent);
951
+ return true;
952
+ }
953
+ if (new_char !== "") {
954
+ // Prevent the original character being insereted
955
+ e.preventDefault();
956
+ // Insert the accented character a the cursor position
957
+ this.insertRawText(new_char, false);
958
+ return true;
959
+ }
960
+ // Mechanism 2 : Automatically convert a/` into à, for example
961
+ // if (ACCENT_KEYS.includes(e.key)) {
962
+ // let pos = e.target.selectionStart
963
+ // let text = e.target.value
964
+ // if (pos < 2)
965
+ // return
966
+ // if (text.charAt(pos-1) !== '/')
967
+ // return
968
+ // let letters = LETTERS[e.key]
969
+ // let old_letter = text.charAt(pos-2)
970
+ // if (letters.hasOwnProperty(old_letter)) {
971
+ // e.preventDefault()
972
+ // text = text.substring(0,pos-2) + letters[old_letter] + text.substring(pos)
973
+ // this.setState({value:text},
974
+ // () => {e.target.selectionStart = e.target.selectionEnd = pos - 1
975
+ // })
976
+ // return true
977
+ // }
978
+ // }
979
+ // if (CTRL_ALT_SHIFT_KEYS.includes(e.key)) {
980
+ // let pos = e.target.selectionStart
981
+ // let text = e.target.value
982
+ // if (pos < 1)
983
+ // return
984
+ // if (text.charAt(pos-1) !== '/')
985
+ // return
986
+ // e.preventDefault()
987
+ // text = text.substring(0,pos-1) + CTRL_ALT_SHIFT_LETTERS[e.key] + text.substring(pos)
988
+ // this.setState({value:text},
989
+ // () => {e.target.selectionStart = e.target.selectionEnd = pos
990
+ // })
991
+ // return true
992
+ // }
993
+ };
994
+
782
995
  handleKeyDown = (e) => {
783
996
  try {
784
997
  // Lets set up or update the undo redo history
@@ -801,37 +1014,53 @@ export default class MathRichInput extends React.Component {
801
1014
  e.key === "6" ||
802
1015
  e.key === "^" ||
803
1016
  e.key === "-" ||
804
- e.key === "_")
1017
+ e.key === "_" ||
1018
+ e.key === "+" ||
1019
+ e.key === "=") &&
1020
+ this.enableHtml()
805
1021
  ) {
806
- if (!this.enableHtml()) {
807
- e.preventDefault();
808
- return;
809
- }
810
1022
  if (e.key === "b") this.styleText(e, "Bold");
811
1023
  if (e.key === "i") this.styleText(e, "Italic");
812
1024
  if (e.key === "u") this.styleText(e, "Underline");
813
- if (e.key === "6" || e.key === "^") this.styleText(e, "Superscript");
814
1025
 
815
- // don't apply subscript if it is the browser command key for "smaller"
1026
+ // Don't apply superscript is ctrl ^, this is reserved for creating accents
816
1027
  if (
817
1028
  /(Win)/i.test(window.navigator.platform) &&
818
1029
  e.ctrlKey &&
819
- e.key === "-"
820
- )
821
- return;
822
- if (
823
- /(Mac)/i.test(window.navigator.platform) &&
824
- e.metaKey &&
825
- e.key === "-"
1030
+ (e.key === "6" || e.key === "^")
826
1031
  )
827
1032
  return;
828
1033
  // otherwise
1034
+ if (e.key === "6" || e.key === "^" || e.key === "+" || e.key === "=")
1035
+ this.styleText(e, "Superscript");
1036
+ // Don't apply subscript if it is the browser command key for "smaller"
1037
+ // if (/(Win)/i.test(window.navigator.platform) && e.ctrlKey && e.key === "-")
1038
+ // return
1039
+ // if (/(Mac)/i.test(window.navigator.platform) && e.metaKey && e.key === "-")
1040
+ // return
1041
+ // otherwise
829
1042
  if (e.key === "-" || e.key === "_") this.styleText(e, "Subscript");
830
1043
 
831
1044
  e.preventDefault();
832
1045
  return;
833
1046
  }
834
1047
 
1048
+ if ((e.metaKey || e.ctrlKey) && e.key === "v") {
1049
+ console.log("Executing paste");
1050
+ document.execCommand("paste");
1051
+ return;
1052
+ }
1053
+ if ((e.metaKey || e.ctrlKey) && e.key === "c") {
1054
+ console.log("Executing copy");
1055
+ document.execCommand("copy");
1056
+ return;
1057
+ }
1058
+ if ((e.metaKey || e.ctrlKey) && e.key === "x") {
1059
+ console.log("Executing cut");
1060
+ document.execCommand("cut");
1061
+ return;
1062
+ }
1063
+
835
1064
  // Ignore automatic key repeat presses on Windows so that we can show an accent bar on
836
1065
  // long key press
837
1066
  if (!/(Win)/i.test(window.navigator.platform)) {
@@ -964,6 +1193,8 @@ export default class MathRichInput extends React.Component {
964
1193
  }
965
1194
  }
966
1195
 
1196
+ if (this.handleKeyDownAccentHelper(e)) return;
1197
+
967
1198
  // Check if inserting character in element that starts with small space
968
1199
  if (
969
1200
  e.key.length === 1 &&
@@ -1120,7 +1351,19 @@ export default class MathRichInput extends React.Component {
1120
1351
  updateActiveButtons = () => {
1121
1352
  try {
1122
1353
  let rangeParams = this._getRangeParams();
1123
- if (rangeParams.startNodeIndex < 0) return;
1354
+ if (rangeParams === null || rangeParams === undefined) {
1355
+ console.warn(
1356
+ "Unable to updateActiveButtons. rangeParams: " + rangeParams
1357
+ );
1358
+ return;
1359
+ }
1360
+ if (rangeParams.startNodeIndex < 0) {
1361
+ console.warn(
1362
+ "Unable to updateActiveButtons. rangeParams.startNodeIndex: " +
1363
+ rangeParams.startNodeIndex
1364
+ );
1365
+ return;
1366
+ }
1124
1367
  let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
1125
1368
  let bold = false;
1126
1369
  let italic = false;
@@ -1166,12 +1409,48 @@ export default class MathRichInput extends React.Component {
1166
1409
  }
1167
1410
  };
1168
1411
 
1412
+ handlePaste = (event) => {
1413
+ let clipboardData = event.clipboardData || window.clipboardData;
1414
+ if (clipboardData === null || clipboardData === undefined) {
1415
+ console.warn("handlePaste called, but no clipboard data");
1416
+ return;
1417
+ }
1418
+ for (let i = 0; i < clipboardData.types.length; i++)
1419
+ console.log(
1420
+ clipboardData.types[i] +
1421
+ ": " +
1422
+ clipboardData.getData(clipboardData.types[i])
1423
+ );
1424
+ let pasteHtml = clipboardData.getData("text/html");
1425
+ if (pasteHtml !== null && pasteHtml.length > 0) {
1426
+ console.log("** Paste html");
1427
+ console.log(pasteHtml);
1428
+ pasteHtml = sanitiseHtml(pasteHtml);
1429
+ console.log("** Santised paste html");
1430
+ console.log(pasteHtml);
1431
+ this.insertHtml(pasteHtml);
1432
+ event.preventDefault();
1433
+ } else {
1434
+ let paste = clipboardData.getData("text");
1435
+ const selection = window.getSelection();
1436
+ if (!selection.rangeCount) {
1437
+ console.warn("handlePaste called, but unable to get selection");
1438
+ return false;
1439
+ }
1440
+ event.preventDefault();
1441
+ selection.deleteFromDocument();
1442
+ this.insertRawText(paste);
1443
+ }
1444
+ };
1445
+
1169
1446
  componentDidMount() {
1170
1447
  try {
1171
1448
  setupKatex();
1172
1449
 
1173
1450
  this.editableDiv.addEventListener("keydown", this.handleKeyDown);
1174
1451
  this.editableDiv.addEventListener("keyup", this.handleKeyUp);
1452
+ this.editableDiv.addEventListener("paste", this.handlePaste);
1453
+ if (this.props.autofocus === true) this.editableDiv.focus();
1175
1454
  } catch (error) {
1176
1455
  console.error(error);
1177
1456
  }
@@ -1181,6 +1460,7 @@ export default class MathRichInput extends React.Component {
1181
1460
  try {
1182
1461
  this.editableDiv.removeEventListener("keydown", this.handleKeyDown);
1183
1462
  this.editableDiv.removeEventListener("keyup", this.handleKeyUp);
1463
+ this.editableDiv.removeEventListener("paste", this.handlePaste);
1184
1464
  } catch (error) {
1185
1465
  console.error(error);
1186
1466
  }
@@ -1206,8 +1486,6 @@ export default class MathRichInput extends React.Component {
1206
1486
  let rangeParams = this._getRangeParams();
1207
1487
  if (rangeParams.startNodeIndex < 0) {
1208
1488
  console.error("Could not find position of cursor in node list");
1209
- console.log(rangeParams);
1210
- console.log(this.editableDiv.childNodes);
1211
1489
 
1212
1490
  this.setOldRangeParams({
1213
1491
  startNodeIndex: 2,
@@ -1287,7 +1565,9 @@ export default class MathRichInput extends React.Component {
1287
1565
  node = node.parentNode;
1288
1566
  } while (node !== this.editableDiv && node !== null);
1289
1567
 
1290
- if (!isLatex) return;
1568
+ if (!isLatex) {
1569
+ return;
1570
+ }
1291
1571
 
1292
1572
  this.showEquationEditorOnMouseUp = true;
1293
1573
  // Save current range parameters so that we can restore them later if we wish
@@ -1448,21 +1728,6 @@ export default class MathRichInput extends React.Component {
1448
1728
  latex,
1449
1729
  this.enableHtml()
1450
1730
  );
1451
- // let new_raw_text =
1452
- // this.markedRawText.substring(0,start_pos) +
1453
- // RAW_TEXT_MATH_START_TAG + latex + RAW_TEXT_MATH_END_TAG +
1454
- // this.markedRawText.substring(end_pos + MARK.length)
1455
-
1456
- // console.log(
1457
- // "Old cursor pos: " +
1458
- // oldRangeParams.startNodeIndex +
1459
- // "->" +
1460
- // oldRangeParams.startOffset
1461
- // );
1462
- // console.log("Old raw text: " + this.props.value);
1463
- // console.log("Old raw text length: " + this.props.value.length);
1464
- // console.log("Old marked raw text:" + this.markedRawText);
1465
- // console.log("New raw text: " + new_raw_text);
1466
1731
 
1467
1732
  let new_rangeParams = this.getOldRangeParams();
1468
1733
  if (this.moveCursorOnInsert) {
@@ -1519,7 +1784,36 @@ export default class MathRichInput extends React.Component {
1519
1784
  styleText = (event, style) => {
1520
1785
  try {
1521
1786
  style = style.toLowerCase();
1522
- let success = document.execCommand(style, false, null);
1787
+ let success = false;
1788
+ // This first part is a workaround whereby executing subscript or superscript
1789
+ // within an existing subscript or superscript makes the text even smaller instead
1790
+ // of toggling it off. Thus we manually toggle it off with some jigerry pokery!
1791
+ if (
1792
+ (style === "subscript" && this.state.activeButtons.subscript) ||
1793
+ (style === "superscript" && this.state.activeButtons.superscript)
1794
+ ) {
1795
+ const selection = document.getSelection();
1796
+ let range = selection.getRangeAt(0);
1797
+ if (
1798
+ range.endContainer === range.startContainer &&
1799
+ range.endOffset === range.startOffset
1800
+ ) {
1801
+ success = document.execCommand("insertText", false, " ");
1802
+ range.setEnd(range.endContainer, range.endOffset + 1);
1803
+ selection.removeAllRanges();
1804
+ selection.addRange(range);
1805
+ }
1806
+ success = document.execCommand("removeFormat", false, null);
1807
+ if (this.state.activeButtons.bold)
1808
+ success = document.execCommand("bold", false, null);
1809
+ if (this.state.activeButtons.italic)
1810
+ success = document.execCommand("italic", false, null);
1811
+ if (this.state.activeButtons.underline)
1812
+ success = document.execCommand("underline", false, null);
1813
+ } else {
1814
+ // This is the normal code ...
1815
+ success = document.execCommand(style, false, null);
1816
+ }
1523
1817
  let buttonState = this.state.activeButtons[style];
1524
1818
  if (buttonState === null || buttonState === undefined) return success;
1525
1819
 
@@ -1569,7 +1863,7 @@ export default class MathRichInput extends React.Component {
1569
1863
  }
1570
1864
  };
1571
1865
 
1572
- insertRawText = (event, new_text) => {
1866
+ insertRawText = (new_text, selectInsertedText = true) => {
1573
1867
  try {
1574
1868
  let rangeParams = this._getRangeParams();
1575
1869
  let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
@@ -1586,23 +1880,171 @@ export default class MathRichInput extends React.Component {
1586
1880
  new_raw_text = removeMarks(
1587
1881
  insertCharacterBeforeMarks(raw_text, new_text)
1588
1882
  );
1589
- if (
1590
- rangeParams.startNodeIndex === rangeParams.endNodeIndex &&
1591
- rangeParams.startOffset === rangeParams.endOffset
1592
- )
1593
- new_rangeParams.startOffset =
1594
- new_rangeParams.startOffset + new_text.length;
1883
+ // console.log("New raw text:")
1884
+ // console.log(new_raw_text)
1885
+ // if (rangeParams.startNodeIndex === rangeParams.endNodeIndex &&
1886
+ // rangeParams.startOffset === rangeParams.endOffset)
1887
+ // new_rangeParams.startOffset = new_rangeParams.startOffset + new_text.length
1595
1888
  new_rangeParams.endOffset = new_rangeParams.endOffset + new_text.length;
1596
-
1889
+ if (!selectInsertedText) {
1890
+ new_rangeParams.startNodeIndex = new_rangeParams.endNodeIndex;
1891
+ new_rangeParams.startOffset = new_rangeParams.endOffset;
1892
+ }
1893
+ // console.log("Setting new range params: ")
1894
+ // console.log(new_rangeParams)
1895
+ // console.log("Setting new mime-type: ")
1896
+ // console.log(this.props.mimeType)
1597
1897
  new_raw_text = removeEncodingIfPlainText(
1598
1898
  new_raw_text,
1599
- this.state.mimeType,
1899
+ this.props.mimeType,
1600
1900
  this.enableHtml()
1601
1901
  );
1602
-
1603
1902
  this.applyChangesToComponent(
1604
1903
  new_raw_text,
1605
- this.state.mimeType,
1904
+ this.props.mimeType,
1905
+ new_rangeParams,
1906
+ this.props.useExpertMode,
1907
+ this.props.selectedTab
1908
+ );
1909
+ } catch (error) {
1910
+ console.error(error);
1911
+ }
1912
+ };
1913
+ // https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
1914
+ pasteHtmlAtCaret = (html, selectPastedContent) => {
1915
+ var sel, range;
1916
+ if (window.getSelection) {
1917
+ // IE9 and non-IE
1918
+ sel = window.getSelection();
1919
+ if (sel.getRangeAt && sel.rangeCount) {
1920
+ range = sel.getRangeAt(0);
1921
+ range.deleteContents();
1922
+ // Range.createContextualFragment() would be useful here but is
1923
+ // only relatively recently standardized and is not supported in
1924
+ // some browsers (IE9, for one)
1925
+ var el = document.createElement("div");
1926
+ el.innerHTML = html;
1927
+ var frag = document.createDocumentFragment(),
1928
+ node,
1929
+ lastNode;
1930
+ while ((node = el.firstChild)) {
1931
+ lastNode = frag.appendChild(node);
1932
+ }
1933
+ var firstNode = frag.firstChild;
1934
+ range.insertNode(frag);
1935
+ // Preserve the selection
1936
+ if (lastNode) {
1937
+ range = range.cloneRange();
1938
+ range.setStartAfter(lastNode);
1939
+ if (selectPastedContent) {
1940
+ range.setStartBefore(firstNode);
1941
+ } else {
1942
+ range.collapse(true);
1943
+ }
1944
+ sel.removeAllRanges();
1945
+ sel.addRange(range);
1946
+ }
1947
+ }
1948
+ } else if ((sel = document.selection) && sel.type != "Control") {
1949
+ // IE < 9
1950
+ var originalRange = sel.createRange();
1951
+ originalRange.collapse(true);
1952
+ sel.createRange().pasteHTML(html);
1953
+ if (selectPastedContent) {
1954
+ range = sel.createRange();
1955
+ range.setEndPoint("StartToStart", originalRange);
1956
+ range.select();
1957
+ }
1958
+ }
1959
+ };
1960
+ insertHtml = (new_html) => {
1961
+ try {
1962
+ // console.log("New html: "+new_html)
1963
+ // console.log("inserting: "+new_html)
1964
+ this.pasteHtmlAtCaret(new_html, true);
1965
+ let new_rangeParams = null;
1966
+ // console.log("New range params: ")
1967
+ try {
1968
+ new_rangeParams = this._getRangeParams();
1969
+ // console.log("New range params: ")
1970
+ // console.log(new_rangeParams)
1971
+ } catch (error) {
1972
+ console.error(error);
1973
+ }
1974
+ // TODO: Fix the bug that fails to get range params correctly
1975
+ if (
1976
+ new_rangeParams.startNodeIndex < 0 ||
1977
+ new_rangeParams.endNodeIndex < 0
1978
+ ) {
1979
+ console.warn(
1980
+ "Selection range params were not fetched correctly. Unable to set highlight inserted text."
1981
+ );
1982
+ }
1983
+ // Even if fetches valid range params, they are probably incorrect
1984
+ // So lets clear them for now
1985
+ new_rangeParams = {
1986
+ startNodeIndex: 0,
1987
+ startOffset: 0,
1988
+ endNodeIndex: 0,
1989
+ endOffset: 0,
1990
+ };
1991
+ let new_raw_text = elementToMarkedRawText(
1992
+ this.editableDiv,
1993
+ null,
1994
+ -1,
1995
+ this.enableHtml()
1996
+ );
1997
+ // console.log("New raw text:"+new_raw_text)
1998
+ let nodeCounts = this._countNodeTypes();
1999
+ let mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.math > 0);
2000
+ new_raw_text = removeEncodingIfPlainText(
2001
+ new_raw_text,
2002
+ mimeType,
2003
+ this.enableHtml()
2004
+ );
2005
+ // OK, got to remove the double <p> and double </p> inserted
2006
+ // After inserting <p>a</p><p>b</p> into <p>xy</p> we will have
2007
+ // <P>x<P>a</P><P>b</P>y</P> and we want <P>xa</P><P>by</P>
2008
+ try {
2009
+ let i = -1;
2010
+ do {
2011
+ i = new_raw_text.indexOf("<P>", i + 1);
2012
+ if (i >= 0) {
2013
+ let j = new_raw_text.indexOf("<P>", i + 1);
2014
+ let k = new_raw_text.indexOf("</P>", i + 1);
2015
+ if (j >= 0) {
2016
+ if (k < 0 || k > j) {
2017
+ new_raw_text =
2018
+ new_raw_text.substring(0, j) +
2019
+ new_raw_text.substring(j + 3, new_raw_text.length);
2020
+ i = -1;
2021
+ }
2022
+ }
2023
+ }
2024
+ } while (i >= 0 && i < new_raw_text.length - 4);
2025
+ i = -1;
2026
+ do {
2027
+ i = new_raw_text.indexOf("</P>", i + 1);
2028
+ if (i >= 0) {
2029
+ let j = new_raw_text.indexOf("</P>", i + 1);
2030
+ let k = new_raw_text.indexOf("<P>", i + 1);
2031
+ if (j >= 0) {
2032
+ if (k < 0 || k > j) {
2033
+ new_raw_text =
2034
+ new_raw_text.substring(0, i) +
2035
+ new_raw_text.substring(i + 4, new_raw_text.length);
2036
+ i = -1;
2037
+ }
2038
+ }
2039
+ }
2040
+ } while (i >= 0 && i < new_raw_text.length - 4);
2041
+ } catch (error) {
2042
+ console.error(error);
2043
+ }
2044
+ // console.log("New raw text:"+new_raw_text)
2045
+ this.applyChangesToComponent(
2046
+ new_raw_text,
2047
+ mimeType,
1606
2048
  new_rangeParams,
1607
2049
  this.props.useExpertMode,
1608
2050
  this.props.selectedTab
@@ -1636,21 +2078,18 @@ export default class MathRichInput extends React.Component {
1636
2078
  new_rangeParams.startOffset = new_offset;
1637
2079
  new_rangeParams.endNodeIndex = rangeParams.endNodeIndex - 1;
1638
2080
  new_rangeParams.endOffset = new_offset;
1639
-
1640
- // console.log("About to apply new range params");
1641
- // console.log(new_rangeParams);
1642
- // console.log("Old nodes");
1643
- // console.log(this.editableDiv.childNodes);
2081
+ new_rangeParams.srartGlobalOffset--;
2082
+ new_rangeParams.endGlobalOffset--;
1644
2083
 
1645
2084
  new_raw_text = removeEncodingIfPlainText(
1646
2085
  new_raw_text,
1647
- this.state.mimeType,
2086
+ this.props.mimeType,
1648
2087
  this.enableHtml()
1649
2088
  );
1650
2089
 
1651
2090
  this.applyChangesToComponent(
1652
2091
  new_raw_text,
1653
- this.state.mimeType,
2092
+ this.props.mimeType,
1654
2093
  new_rangeParams,
1655
2094
  this.props.useExpertMode,
1656
2095
  this.props.selectedTab
@@ -1675,6 +2114,11 @@ export default class MathRichInput extends React.Component {
1675
2114
  let new_raw_text = null;
1676
2115
  let new_rangeParams = { ...rangeParams };
1677
2116
 
2117
+ if (new_rangeParams.startGlobalOffset) {
2118
+ new_rangeParams.startGlobalOffset++;
2119
+ new_rangeParams.endGlobalOffset++;
2120
+ }
2121
+
1678
2122
  if (this.enableHtml()) {
1679
2123
  new_raw_text = removeMarks(
1680
2124
  insertCharacterBeforeMarks(raw_text, "</p><p>")
@@ -1692,20 +2136,15 @@ export default class MathRichInput extends React.Component {
1692
2136
  new_rangeParams.endOffset++;
1693
2137
  }
1694
2138
 
1695
- // console.log("About to apply new range params");
1696
- // console.log(new_rangeParams);
1697
- // console.log("Old nodes");
1698
- // console.log(this.editableDiv.childNodes);
1699
-
1700
2139
  new_raw_text = removeEncodingIfPlainText(
1701
2140
  new_raw_text,
1702
- this.state.mimeType,
2141
+ this.props.mimeType,
1703
2142
  this.enableHtml()
1704
2143
  );
1705
2144
 
1706
2145
  this.applyChangesToComponent(
1707
2146
  new_raw_text,
1708
- this.state.mimeType,
2147
+ this.props.mimeType,
1709
2148
  new_rangeParams,
1710
2149
  this.props.useExpertMode,
1711
2150
  this.props.selectedTab
@@ -1743,12 +2182,12 @@ export default class MathRichInput extends React.Component {
1743
2182
 
1744
2183
  new_raw_text = removeEncodingIfPlainText(
1745
2184
  new_raw_text,
1746
- this.state.mimeType,
2185
+ this.props.mimeType,
1747
2186
  this.enableHtml()
1748
2187
  );
1749
2188
  this.applyChangesToComponent(
1750
2189
  new_raw_text,
1751
- this.state.mimeType,
2190
+ this.props.mimeType,
1752
2191
  new_rangeParams,
1753
2192
  this.props.useExpertMode,
1754
2193
  this.props.selectedTab
@@ -1772,6 +2211,9 @@ export default class MathRichInput extends React.Component {
1772
2211
  );
1773
2212
  let new_raw_text = null;
1774
2213
  let new_rangeParams = { ...rangeParams };
2214
+ new_rangeParams.startGlobalOffset++;
2215
+ new_rangeParams.endGlobalOffset++;
2216
+
1775
2217
  if (old_char === null) {
1776
2218
  new_raw_text = removeMarks(
1777
2219
  insertCharacterBeforeMarks(raw_text, new_char)
@@ -1790,12 +2232,12 @@ export default class MathRichInput extends React.Component {
1790
2232
 
1791
2233
  new_raw_text = removeEncodingIfPlainText(
1792
2234
  new_raw_text,
1793
- this.state.mimeType,
2235
+ this.props.mimeType,
1794
2236
  this.enableHtml()
1795
2237
  );
1796
2238
  this.applyChangesToComponent(
1797
2239
  new_raw_text,
1798
- this.state.mimeType,
2240
+ this.props.mimeType,
1799
2241
  new_rangeParams,
1800
2242
  this.props.useExpertMode,
1801
2243
  this.props.selectedTab
@@ -1814,6 +2256,10 @@ export default class MathRichInput extends React.Component {
1814
2256
  }
1815
2257
  };
1816
2258
 
2259
+ focus = () => {
2260
+ this.editableDiv.focus();
2261
+ };
2262
+
1817
2263
  handleFocus = (event) => {
1818
2264
  const { onFocus } = this.props;
1819
2265
  if (onFocus != null) {