@zzish/math-rich-input 0.1.24 → 0.1.25

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();
@@ -566,7 +677,20 @@ export default class MathRichInput extends React.Component {
566
677
  new_offset = 0;
567
678
  }
568
679
 
569
- if (new_index === -1 || new_offset === -1) return;
680
+ if (new_index === -1 || new_offset === -1) {
681
+ return;
682
+ } else {
683
+ let new_rangeParams = {
684
+ startNodeIndex: new_index,
685
+ startOffset: new_offset,
686
+ endNodeIndex: new_index,
687
+ endOffset: new_offset,
688
+ startGlobalOffset: new_globalOffset,
689
+ endGlobalOffset: new_globalOffset,
690
+ };
691
+ this._setRangeParams(new_rangeParams);
692
+ e.preventDefault();
693
+ }
570
694
 
571
695
  let new_rangeParams = {
572
696
  startNodeIndex: new_index,
@@ -585,6 +709,15 @@ export default class MathRichInput extends React.Component {
585
709
  let index = rangeParams.startNodeIndex;
586
710
  let offset = rangeParams.startOffset;
587
711
 
712
+ let new_globalOffset = rangeParams.startGlobalOffset + 1;
713
+ let new_rangeParams = {
714
+ startGlobalOffset: new_globalOffset,
715
+ endGlobalOffset: new_globalOffset,
716
+ };
717
+ e.preventDefault();
718
+ this._setRangeParams(new_rangeParams);
719
+ return;
720
+
588
721
  if (index < 0)
589
722
  // Empty input field
590
723
  return;
@@ -720,20 +853,32 @@ export default class MathRichInput extends React.Component {
720
853
  }
721
854
  if (!convert_to_latex) return;
722
855
 
856
+ // So we will convert to latex
857
+ // If we get the raw text without replacing the dollars with marks, the dollars
858
+ // will be converted to html codes. So we will replace them with marks and then
859
+ // replace the marks with dollars later
860
+ // Remove first dollar and insert marks into the node instead of dollars
861
+ let latex = node.nodeValue.substring(
862
+ first_dollar_pos + 1,
863
+ second_dollar_pos
864
+ );
865
+
723
866
  node.nodeValue =
724
867
  node.nodeValue.substring(0, first_dollar_pos) +
725
868
  MARK +
726
- node.nodeValue.substring(first_dollar_pos + 1, second_dollar_pos) +
869
+ latex +
727
870
  MARK +
728
871
  node.nodeValue.substring(second_dollar_pos);
729
872
 
873
+ // Get the raw text and now put the dollars back
730
874
  let raw_text = elementToMarkedRawText(
731
875
  e.target,
732
876
  null,
733
877
  0,
734
878
  this.enableHtml()
735
879
  );
736
- raw_text = raw_text.replace(MARK_REG_EXP, "$");
880
+ // now replace the marks with math
881
+ raw_text = replaceMarksWithMath(raw_text, latex, this.enableHtml());
737
882
 
738
883
  // We are turning one node into three and want the cursor at the start of the third node
739
884
  let new_rangeParams = {
@@ -779,6 +924,85 @@ export default class MathRichInput extends React.Component {
779
924
  e.preventDefault();
780
925
  };
781
926
 
927
+ handleKeyDownAccentHelper = (e) => {
928
+ // console.log(e.ctrlKey)
929
+ // console.log(e.altKey)
930
+ // console.log(e.shiftKey)
931
+ let new_accent = "";
932
+ let new_char = "";
933
+ // console.log(e.key)
934
+ if (e.ctrlKey) {
935
+ // First check for CTRL+ALT+SHIFT keys
936
+ if (e.shiftKey && e.altKey) {
937
+ console.log(e.key);
938
+ if (CTRL_ALT_SHIFT_LETTERS.hasOwnProperty(e.key))
939
+ new_char = CTRL_ALT_SHIFT_LETTERS[e.key];
940
+ }
941
+ // Second check special case of CTRL-tilde
942
+ else if (e.shiftKey && e.key === "`") new_accent = "~";
943
+ // Finally check for normal accent keys
944
+ else if (LETTERS.hasOwnProperty(e.key)) new_accent = e.key;
945
+ } else if (this.accent !== "") {
946
+ // Ignore key events for shift and caps lock as that can mean the user
947
+ // is simply changing to or from capitals and we don't want to reset
948
+ // this.accent in this case
949
+ if (e.key === "Shift") return false;
950
+ if (e.key === "CapsLock") return false;
951
+ // Get the possible letters that use this accent and test to see if the
952
+ // current key pressed is one of those letters.
953
+ let letters = LETTERS[this.accent];
954
+ if (letters.hasOwnProperty(e.key)) new_char = letters[e.key];
955
+ }
956
+ // If a new accent has been set, store it in this.accent, otherwise clear this.accent
957
+ if (new_accent === "") this.accent = "";
958
+ else {
959
+ e.preventDefault();
960
+ this.accent = new_accent;
961
+ console.log("Accent set: " + this.accent);
962
+ return true;
963
+ }
964
+ if (new_char !== "") {
965
+ // Prevent the original character being insereted
966
+ e.preventDefault();
967
+ // Insert the accented character a the cursor position
968
+ this.insertRawText(new_char, false);
969
+ return true;
970
+ }
971
+ // Mechanism 2 : Automatically convert a/` into à, for example
972
+ // if (ACCENT_KEYS.includes(e.key)) {
973
+ // let pos = e.target.selectionStart
974
+ // let text = e.target.value
975
+ // if (pos < 2)
976
+ // return
977
+ // if (text.charAt(pos-1) !== '/')
978
+ // return
979
+ // let letters = LETTERS[e.key]
980
+ // let old_letter = text.charAt(pos-2)
981
+ // if (letters.hasOwnProperty(old_letter)) {
982
+ // e.preventDefault()
983
+ // text = text.substring(0,pos-2) + letters[old_letter] + text.substring(pos)
984
+ // this.setState({value:text},
985
+ // () => {e.target.selectionStart = e.target.selectionEnd = pos - 1
986
+ // })
987
+ // return true
988
+ // }
989
+ // }
990
+ // if (CTRL_ALT_SHIFT_KEYS.includes(e.key)) {
991
+ // let pos = e.target.selectionStart
992
+ // let text = e.target.value
993
+ // if (pos < 1)
994
+ // return
995
+ // if (text.charAt(pos-1) !== '/')
996
+ // return
997
+ // e.preventDefault()
998
+ // text = text.substring(0,pos-1) + CTRL_ALT_SHIFT_LETTERS[e.key] + text.substring(pos)
999
+ // this.setState({value:text},
1000
+ // () => {e.target.selectionStart = e.target.selectionEnd = pos
1001
+ // })
1002
+ // return true
1003
+ // }
1004
+ };
1005
+
782
1006
  handleKeyDown = (e) => {
783
1007
  try {
784
1008
  // Lets set up or update the undo redo history
@@ -801,37 +1025,53 @@ export default class MathRichInput extends React.Component {
801
1025
  e.key === "6" ||
802
1026
  e.key === "^" ||
803
1027
  e.key === "-" ||
804
- e.key === "_")
1028
+ e.key === "_" ||
1029
+ e.key === "+" ||
1030
+ e.key === "=") &&
1031
+ this.enableHtml()
805
1032
  ) {
806
- if (!this.enableHtml()) {
807
- e.preventDefault();
808
- return;
809
- }
810
1033
  if (e.key === "b") this.styleText(e, "Bold");
811
1034
  if (e.key === "i") this.styleText(e, "Italic");
812
1035
  if (e.key === "u") this.styleText(e, "Underline");
813
- if (e.key === "6" || e.key === "^") this.styleText(e, "Superscript");
814
1036
 
815
- // don't apply subscript if it is the browser command key for "smaller"
1037
+ // Don't apply superscript is ctrl ^, this is reserved for creating accents
816
1038
  if (
817
1039
  /(Win)/i.test(window.navigator.platform) &&
818
1040
  e.ctrlKey &&
819
- e.key === "-"
820
- )
821
- return;
822
- if (
823
- /(Mac)/i.test(window.navigator.platform) &&
824
- e.metaKey &&
825
- e.key === "-"
1041
+ (e.key === "6" || e.key === "^")
826
1042
  )
827
1043
  return;
828
1044
  // otherwise
1045
+ if (e.key === "6" || e.key === "^" || e.key === "+" || e.key === "=")
1046
+ this.styleText(e, "Superscript");
1047
+ // Don't apply subscript if it is the browser command key for "smaller"
1048
+ // if (/(Win)/i.test(window.navigator.platform) && e.ctrlKey && e.key === "-")
1049
+ // return
1050
+ // if (/(Mac)/i.test(window.navigator.platform) && e.metaKey && e.key === "-")
1051
+ // return
1052
+ // otherwise
829
1053
  if (e.key === "-" || e.key === "_") this.styleText(e, "Subscript");
830
1054
 
831
1055
  e.preventDefault();
832
1056
  return;
833
1057
  }
834
1058
 
1059
+ if ((e.metaKey || e.ctrlKey) && e.key === "v") {
1060
+ console.log("Executing paste");
1061
+ document.execCommand("paste");
1062
+ return;
1063
+ }
1064
+ if ((e.metaKey || e.ctrlKey) && e.key === "c") {
1065
+ console.log("Executing copy");
1066
+ document.execCommand("copy");
1067
+ return;
1068
+ }
1069
+ if ((e.metaKey || e.ctrlKey) && e.key === "x") {
1070
+ console.log("Executing cut");
1071
+ document.execCommand("cut");
1072
+ return;
1073
+ }
1074
+
835
1075
  // Ignore automatic key repeat presses on Windows so that we can show an accent bar on
836
1076
  // long key press
837
1077
  if (!/(Win)/i.test(window.navigator.platform)) {
@@ -964,6 +1204,8 @@ export default class MathRichInput extends React.Component {
964
1204
  }
965
1205
  }
966
1206
 
1207
+ if (this.handleKeyDownAccentHelper(e)) return;
1208
+
967
1209
  // Check if inserting character in element that starts with small space
968
1210
  if (
969
1211
  e.key.length === 1 &&
@@ -1120,7 +1362,19 @@ export default class MathRichInput extends React.Component {
1120
1362
  updateActiveButtons = () => {
1121
1363
  try {
1122
1364
  let rangeParams = this._getRangeParams();
1123
- if (rangeParams.startNodeIndex < 0) return;
1365
+ if (rangeParams === null || rangeParams === undefined) {
1366
+ console.warn(
1367
+ "Unable to updateActiveButtons. rangeParams: " + rangeParams
1368
+ );
1369
+ return;
1370
+ }
1371
+ if (rangeParams.startNodeIndex < 0) {
1372
+ console.warn(
1373
+ "Unable to updateActiveButtons. rangeParams.startNodeIndex: " +
1374
+ rangeParams.startNodeIndex
1375
+ );
1376
+ return;
1377
+ }
1124
1378
  let node = this._findNodeWithIndex(rangeParams.startNodeIndex);
1125
1379
  let bold = false;
1126
1380
  let italic = false;
@@ -1166,12 +1420,48 @@ export default class MathRichInput extends React.Component {
1166
1420
  }
1167
1421
  };
1168
1422
 
1423
+ handlePaste = (event) => {
1424
+ let clipboardData = event.clipboardData || window.clipboardData;
1425
+ if (clipboardData === null || clipboardData === undefined) {
1426
+ console.warn("handlePaste called, but no clipboard data");
1427
+ return;
1428
+ }
1429
+ for (let i = 0; i < clipboardData.types.length; i++)
1430
+ console.log(
1431
+ clipboardData.types[i] +
1432
+ ": " +
1433
+ clipboardData.getData(clipboardData.types[i])
1434
+ );
1435
+ let pasteHtml = clipboardData.getData("text/html");
1436
+ if (pasteHtml !== null && pasteHtml.length > 0) {
1437
+ console.log("** Paste html");
1438
+ console.log(pasteHtml);
1439
+ pasteHtml = sanitiseHtml(pasteHtml);
1440
+ console.log("** Santised paste html");
1441
+ console.log(pasteHtml);
1442
+ this.insertHtml(pasteHtml);
1443
+ event.preventDefault();
1444
+ } else {
1445
+ let paste = clipboardData.getData("text");
1446
+ const selection = window.getSelection();
1447
+ if (!selection.rangeCount) {
1448
+ console.warn("handlePaste called, but unable to get selection");
1449
+ return false;
1450
+ }
1451
+ event.preventDefault();
1452
+ selection.deleteFromDocument();
1453
+ this.insertRawText(paste);
1454
+ }
1455
+ };
1456
+
1169
1457
  componentDidMount() {
1170
1458
  try {
1171
1459
  setupKatex();
1172
1460
 
1173
1461
  this.editableDiv.addEventListener("keydown", this.handleKeyDown);
1174
1462
  this.editableDiv.addEventListener("keyup", this.handleKeyUp);
1463
+ this.editableDiv.addEventListener("paste", this.handlePaste);
1464
+ if (this.props.autofocus === true) this.editableDiv.focus();
1175
1465
  } catch (error) {
1176
1466
  console.error(error);
1177
1467
  }
@@ -1181,6 +1471,7 @@ export default class MathRichInput extends React.Component {
1181
1471
  try {
1182
1472
  this.editableDiv.removeEventListener("keydown", this.handleKeyDown);
1183
1473
  this.editableDiv.removeEventListener("keyup", this.handleKeyUp);
1474
+ this.editableDiv.removeEventListener("paste", this.handlePaste);
1184
1475
  } catch (error) {
1185
1476
  console.error(error);
1186
1477
  }
@@ -1206,8 +1497,6 @@ export default class MathRichInput extends React.Component {
1206
1497
  let rangeParams = this._getRangeParams();
1207
1498
  if (rangeParams.startNodeIndex < 0) {
1208
1499
  console.error("Could not find position of cursor in node list");
1209
- console.log(rangeParams);
1210
- console.log(this.editableDiv.childNodes);
1211
1500
 
1212
1501
  this.setOldRangeParams({
1213
1502
  startNodeIndex: 2,
@@ -1287,7 +1576,9 @@ export default class MathRichInput extends React.Component {
1287
1576
  node = node.parentNode;
1288
1577
  } while (node !== this.editableDiv && node !== null);
1289
1578
 
1290
- if (!isLatex) return;
1579
+ if (!isLatex) {
1580
+ return;
1581
+ }
1291
1582
 
1292
1583
  this.showEquationEditorOnMouseUp = true;
1293
1584
  // Save current range parameters so that we can restore them later if we wish
@@ -1448,21 +1739,6 @@ export default class MathRichInput extends React.Component {
1448
1739
  latex,
1449
1740
  this.enableHtml()
1450
1741
  );
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
1742
 
1467
1743
  let new_rangeParams = this.getOldRangeParams();
1468
1744
  if (this.moveCursorOnInsert) {
@@ -1519,7 +1795,36 @@ export default class MathRichInput extends React.Component {
1519
1795
  styleText = (event, style) => {
1520
1796
  try {
1521
1797
  style = style.toLowerCase();
1522
- let success = document.execCommand(style, false, null);
1798
+ let success = false;
1799
+ // This first part is a workaround whereby executing subscript or superscript
1800
+ // within an existing subscript or superscript makes the text even smaller instead
1801
+ // of toggling it off. Thus we manually toggle it off with some jigerry pokery!
1802
+ if (
1803
+ (style === "subscript" && this.state.activeButtons.subscript) ||
1804
+ (style === "superscript" && this.state.activeButtons.superscript)
1805
+ ) {
1806
+ const selection = document.getSelection();
1807
+ let range = selection.getRangeAt(0);
1808
+ if (
1809
+ range.endContainer === range.startContainer &&
1810
+ range.endOffset === range.startOffset
1811
+ ) {
1812
+ success = document.execCommand("insertText", false, " ");
1813
+ range.setEnd(range.endContainer, range.endOffset + 1);
1814
+ selection.removeAllRanges();
1815
+ selection.addRange(range);
1816
+ }
1817
+ success = document.execCommand("removeFormat", false, null);
1818
+ if (this.state.activeButtons.bold)
1819
+ success = document.execCommand("bold", false, null);
1820
+ if (this.state.activeButtons.italic)
1821
+ success = document.execCommand("italic", false, null);
1822
+ if (this.state.activeButtons.underline)
1823
+ success = document.execCommand("underline", false, null);
1824
+ } else {
1825
+ // This is the normal code ...
1826
+ success = document.execCommand(style, false, null);
1827
+ }
1523
1828
  let buttonState = this.state.activeButtons[style];
1524
1829
  if (buttonState === null || buttonState === undefined) return success;
1525
1830
 
@@ -1569,7 +1874,7 @@ export default class MathRichInput extends React.Component {
1569
1874
  }
1570
1875
  };
1571
1876
 
1572
- insertRawText = (event, new_text) => {
1877
+ insertRawText = (new_text, selectInsertedText = true) => {
1573
1878
  try {
1574
1879
  let rangeParams = this._getRangeParams();
1575
1880
  let node = this._findNodeWithIndex(rangeParams.endNodeIndex);
@@ -1586,23 +1891,171 @@ export default class MathRichInput extends React.Component {
1586
1891
  new_raw_text = removeMarks(
1587
1892
  insertCharacterBeforeMarks(raw_text, new_text)
1588
1893
  );
1589
- if (
1590
- rangeParams.startNodeIndex === rangeParams.endNodeIndex &&
1591
- rangeParams.startOffset === rangeParams.endOffset
1592
- )
1593
- new_rangeParams.startOffset =
1594
- new_rangeParams.startOffset + new_text.length;
1894
+ // console.log("New raw text:")
1895
+ // console.log(new_raw_text)
1896
+ // if (rangeParams.startNodeIndex === rangeParams.endNodeIndex &&
1897
+ // rangeParams.startOffset === rangeParams.endOffset)
1898
+ // new_rangeParams.startOffset = new_rangeParams.startOffset + new_text.length
1595
1899
  new_rangeParams.endOffset = new_rangeParams.endOffset + new_text.length;
1596
-
1900
+ if (!selectInsertedText) {
1901
+ new_rangeParams.startNodeIndex = new_rangeParams.endNodeIndex;
1902
+ new_rangeParams.startOffset = new_rangeParams.endOffset;
1903
+ }
1904
+ // console.log("Setting new range params: ")
1905
+ // console.log(new_rangeParams)
1906
+ // console.log("Setting new mime-type: ")
1907
+ // console.log(this.props.mimeType)
1597
1908
  new_raw_text = removeEncodingIfPlainText(
1598
1909
  new_raw_text,
1599
- this.state.mimeType,
1910
+ this.props.mimeType,
1600
1911
  this.enableHtml()
1601
1912
  );
1602
-
1603
1913
  this.applyChangesToComponent(
1604
1914
  new_raw_text,
1605
- this.state.mimeType,
1915
+ this.props.mimeType,
1916
+ new_rangeParams,
1917
+ this.props.useExpertMode,
1918
+ this.props.selectedTab
1919
+ );
1920
+ } catch (error) {
1921
+ console.error(error);
1922
+ }
1923
+ };
1924
+ // https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
1925
+ pasteHtmlAtCaret = (html, selectPastedContent) => {
1926
+ var sel, range;
1927
+ if (window.getSelection) {
1928
+ // IE9 and non-IE
1929
+ sel = window.getSelection();
1930
+ if (sel.getRangeAt && sel.rangeCount) {
1931
+ range = sel.getRangeAt(0);
1932
+ range.deleteContents();
1933
+ // Range.createContextualFragment() would be useful here but is
1934
+ // only relatively recently standardized and is not supported in
1935
+ // some browsers (IE9, for one)
1936
+ var el = document.createElement("div");
1937
+ el.innerHTML = html;
1938
+ var frag = document.createDocumentFragment(),
1939
+ node,
1940
+ lastNode;
1941
+ while ((node = el.firstChild)) {
1942
+ lastNode = frag.appendChild(node);
1943
+ }
1944
+ var firstNode = frag.firstChild;
1945
+ range.insertNode(frag);
1946
+ // Preserve the selection
1947
+ if (lastNode) {
1948
+ range = range.cloneRange();
1949
+ range.setStartAfter(lastNode);
1950
+ if (selectPastedContent) {
1951
+ range.setStartBefore(firstNode);
1952
+ } else {
1953
+ range.collapse(true);
1954
+ }
1955
+ sel.removeAllRanges();
1956
+ sel.addRange(range);
1957
+ }
1958
+ }
1959
+ } else if ((sel = document.selection) && sel.type != "Control") {
1960
+ // IE < 9
1961
+ var originalRange = sel.createRange();
1962
+ originalRange.collapse(true);
1963
+ sel.createRange().pasteHTML(html);
1964
+ if (selectPastedContent) {
1965
+ range = sel.createRange();
1966
+ range.setEndPoint("StartToStart", originalRange);
1967
+ range.select();
1968
+ }
1969
+ }
1970
+ };
1971
+ insertHtml = (new_html) => {
1972
+ try {
1973
+ // console.log("New html: "+new_html)
1974
+ // console.log("inserting: "+new_html)
1975
+ this.pasteHtmlAtCaret(new_html, true);
1976
+ let new_rangeParams = null;
1977
+ // console.log("New range params: ")
1978
+ try {
1979
+ new_rangeParams = this._getRangeParams();
1980
+ // console.log("New range params: ")
1981
+ // console.log(new_rangeParams)
1982
+ } catch (error) {
1983
+ console.error(error);
1984
+ }
1985
+ // TODO: Fix the bug that fails to get range params correctly
1986
+ if (
1987
+ new_rangeParams.startNodeIndex < 0 ||
1988
+ new_rangeParams.endNodeIndex < 0
1989
+ ) {
1990
+ console.warn(
1991
+ "Selection range params were not fetched correctly. Unable to set highlight inserted text."
1992
+ );
1993
+ }
1994
+ // Even if fetches valid range params, they are probably incorrect
1995
+ // So lets clear them for now
1996
+ new_rangeParams = {
1997
+ startNodeIndex: 0,
1998
+ startOffset: 0,
1999
+ endNodeIndex: 0,
2000
+ endOffset: 0,
2001
+ };
2002
+ let new_raw_text = elementToMarkedRawText(
2003
+ this.editableDiv,
2004
+ null,
2005
+ -1,
2006
+ this.enableHtml()
2007
+ );
2008
+ // console.log("New raw text:"+new_raw_text)
2009
+ let nodeCounts = this._countNodeTypes();
2010
+ let mimeType = this.getMimeType(nodeCounts.html > 0, nodeCounts.math > 0);
2011
+ new_raw_text = removeEncodingIfPlainText(
2012
+ new_raw_text,
2013
+ mimeType,
2014
+ this.enableHtml()
2015
+ );
2016
+ // OK, got to remove the double <p> and double </p> inserted
2017
+ // After inserting <p>a</p><p>b</p> into <p>xy</p> we will have
2018
+ // <P>x<P>a</P><P>b</P>y</P> and we want <P>xa</P><P>by</P>
2019
+ try {
2020
+ let i = -1;
2021
+ do {
2022
+ i = new_raw_text.indexOf("<P>", i + 1);
2023
+ if (i >= 0) {
2024
+ let j = new_raw_text.indexOf("<P>", i + 1);
2025
+ let k = new_raw_text.indexOf("</P>", i + 1);
2026
+ if (j >= 0) {
2027
+ if (k < 0 || k > j) {
2028
+ new_raw_text =
2029
+ new_raw_text.substring(0, j) +
2030
+ new_raw_text.substring(j + 3, new_raw_text.length);
2031
+ i = -1;
2032
+ }
2033
+ }
2034
+ }
2035
+ } while (i >= 0 && i < new_raw_text.length - 4);
2036
+ i = -1;
2037
+ do {
2038
+ i = new_raw_text.indexOf("</P>", i + 1);
2039
+ if (i >= 0) {
2040
+ let j = new_raw_text.indexOf("</P>", i + 1);
2041
+ let k = new_raw_text.indexOf("<P>", i + 1);
2042
+ if (j >= 0) {
2043
+ if (k < 0 || k > j) {
2044
+ new_raw_text =
2045
+ new_raw_text.substring(0, i) +
2046
+ new_raw_text.substring(i + 4, new_raw_text.length);
2047
+ i = -1;
2048
+ }
2049
+ }
2050
+ }
2051
+ } while (i >= 0 && i < new_raw_text.length - 4);
2052
+ } catch (error) {
2053
+ console.error(error);
2054
+ }
2055
+ // console.log("New raw text:"+new_raw_text)
2056
+ this.applyChangesToComponent(
2057
+ new_raw_text,
2058
+ mimeType,
1606
2059
  new_rangeParams,
1607
2060
  this.props.useExpertMode,
1608
2061
  this.props.selectedTab
@@ -1636,21 +2089,18 @@ export default class MathRichInput extends React.Component {
1636
2089
  new_rangeParams.startOffset = new_offset;
1637
2090
  new_rangeParams.endNodeIndex = rangeParams.endNodeIndex - 1;
1638
2091
  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);
2092
+ new_rangeParams.srartGlobalOffset--;
2093
+ new_rangeParams.endGlobalOffset--;
1644
2094
 
1645
2095
  new_raw_text = removeEncodingIfPlainText(
1646
2096
  new_raw_text,
1647
- this.state.mimeType,
2097
+ this.props.mimeType,
1648
2098
  this.enableHtml()
1649
2099
  );
1650
2100
 
1651
2101
  this.applyChangesToComponent(
1652
2102
  new_raw_text,
1653
- this.state.mimeType,
2103
+ this.props.mimeType,
1654
2104
  new_rangeParams,
1655
2105
  this.props.useExpertMode,
1656
2106
  this.props.selectedTab
@@ -1675,6 +2125,11 @@ export default class MathRichInput extends React.Component {
1675
2125
  let new_raw_text = null;
1676
2126
  let new_rangeParams = { ...rangeParams };
1677
2127
 
2128
+ if (new_rangeParams.startGlobalOffset) {
2129
+ new_rangeParams.startGlobalOffset++;
2130
+ new_rangeParams.endGlobalOffset++;
2131
+ }
2132
+
1678
2133
  if (this.enableHtml()) {
1679
2134
  new_raw_text = removeMarks(
1680
2135
  insertCharacterBeforeMarks(raw_text, "</p><p>")
@@ -1692,20 +2147,15 @@ export default class MathRichInput extends React.Component {
1692
2147
  new_rangeParams.endOffset++;
1693
2148
  }
1694
2149
 
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
2150
  new_raw_text = removeEncodingIfPlainText(
1701
2151
  new_raw_text,
1702
- this.state.mimeType,
2152
+ this.props.mimeType,
1703
2153
  this.enableHtml()
1704
2154
  );
1705
2155
 
1706
2156
  this.applyChangesToComponent(
1707
2157
  new_raw_text,
1708
- this.state.mimeType,
2158
+ this.props.mimeType,
1709
2159
  new_rangeParams,
1710
2160
  this.props.useExpertMode,
1711
2161
  this.props.selectedTab
@@ -1743,12 +2193,12 @@ export default class MathRichInput extends React.Component {
1743
2193
 
1744
2194
  new_raw_text = removeEncodingIfPlainText(
1745
2195
  new_raw_text,
1746
- this.state.mimeType,
2196
+ this.props.mimeType,
1747
2197
  this.enableHtml()
1748
2198
  );
1749
2199
  this.applyChangesToComponent(
1750
2200
  new_raw_text,
1751
- this.state.mimeType,
2201
+ this.props.mimeType,
1752
2202
  new_rangeParams,
1753
2203
  this.props.useExpertMode,
1754
2204
  this.props.selectedTab
@@ -1772,6 +2222,9 @@ export default class MathRichInput extends React.Component {
1772
2222
  );
1773
2223
  let new_raw_text = null;
1774
2224
  let new_rangeParams = { ...rangeParams };
2225
+ new_rangeParams.startGlobalOffset++;
2226
+ new_rangeParams.endGlobalOffset++;
2227
+
1775
2228
  if (old_char === null) {
1776
2229
  new_raw_text = removeMarks(
1777
2230
  insertCharacterBeforeMarks(raw_text, new_char)
@@ -1790,12 +2243,12 @@ export default class MathRichInput extends React.Component {
1790
2243
 
1791
2244
  new_raw_text = removeEncodingIfPlainText(
1792
2245
  new_raw_text,
1793
- this.state.mimeType,
2246
+ this.props.mimeType,
1794
2247
  this.enableHtml()
1795
2248
  );
1796
2249
  this.applyChangesToComponent(
1797
2250
  new_raw_text,
1798
- this.state.mimeType,
2251
+ this.props.mimeType,
1799
2252
  new_rangeParams,
1800
2253
  this.props.useExpertMode,
1801
2254
  this.props.selectedTab
@@ -1814,6 +2267,10 @@ export default class MathRichInput extends React.Component {
1814
2267
  }
1815
2268
  };
1816
2269
 
2270
+ focus = () => {
2271
+ this.editableDiv.focus();
2272
+ };
2273
+
1817
2274
  handleFocus = (event) => {
1818
2275
  const { onFocus } = this.props;
1819
2276
  if (onFocus != null) {