@zzish/math-rich-input 0.1.23 → 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.
- package/dist/index.js +6 -1
- package/dist/math-rich-input.css +130 -130
- package/package.json +1 -1
- package/src/MathRichInput.jsx +529 -69
- package/src/equationEditorButtonsHelper.js +17 -8
- package/src/mathRichInputHelper.js +368 -14
package/src/MathRichInput.jsx
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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)
|
|
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 =
|
|
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 = (
|
|
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
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
)
|
|
1593
|
-
|
|
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.
|
|
1910
|
+
this.props.mimeType,
|
|
1600
1911
|
this.enableHtml()
|
|
1601
1912
|
);
|
|
1602
|
-
|
|
1603
1913
|
this.applyChangesToComponent(
|
|
1604
1914
|
new_raw_text,
|
|
1605
|
-
this.
|
|
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
|
-
|
|
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.
|
|
2097
|
+
this.props.mimeType,
|
|
1648
2098
|
this.enableHtml()
|
|
1649
2099
|
);
|
|
1650
2100
|
|
|
1651
2101
|
this.applyChangesToComponent(
|
|
1652
2102
|
new_raw_text,
|
|
1653
|
-
this.
|
|
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.
|
|
2152
|
+
this.props.mimeType,
|
|
1703
2153
|
this.enableHtml()
|
|
1704
2154
|
);
|
|
1705
2155
|
|
|
1706
2156
|
this.applyChangesToComponent(
|
|
1707
2157
|
new_raw_text,
|
|
1708
|
-
this.
|
|
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.
|
|
2196
|
+
this.props.mimeType,
|
|
1747
2197
|
this.enableHtml()
|
|
1748
2198
|
);
|
|
1749
2199
|
this.applyChangesToComponent(
|
|
1750
2200
|
new_raw_text,
|
|
1751
|
-
this.
|
|
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.
|
|
2246
|
+
this.props.mimeType,
|
|
1794
2247
|
this.enableHtml()
|
|
1795
2248
|
);
|
|
1796
2249
|
this.applyChangesToComponent(
|
|
1797
2250
|
new_raw_text,
|
|
1798
|
-
this.
|
|
2251
|
+
this.props.mimeType,
|
|
1799
2252
|
new_rangeParams,
|
|
1800
2253
|
this.props.useExpertMode,
|
|
1801
2254
|
this.props.selectedTab
|
|
@@ -1814,8 +2267,15 @@ 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;
|
|
2276
|
+
if (onFocus != null) {
|
|
2277
|
+
onFocus();
|
|
2278
|
+
}
|
|
1819
2279
|
try {
|
|
1820
2280
|
this._setRangeParams(this.getOldRangeParams());
|
|
1821
2281
|
this.setState({ hasFocus: true });
|