@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.
- package/dist/index.js +935 -267
- package/package.json +1 -1
- package/src/MathRichInput.jsx +523 -77
- 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();
|
|
@@ -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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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)
|
|
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 =
|
|
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 = (
|
|
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
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
)
|
|
1593
|
-
|
|
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.
|
|
1899
|
+
this.props.mimeType,
|
|
1600
1900
|
this.enableHtml()
|
|
1601
1901
|
);
|
|
1602
|
-
|
|
1603
1902
|
this.applyChangesToComponent(
|
|
1604
1903
|
new_raw_text,
|
|
1605
|
-
this.
|
|
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
|
-
|
|
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.
|
|
2086
|
+
this.props.mimeType,
|
|
1648
2087
|
this.enableHtml()
|
|
1649
2088
|
);
|
|
1650
2089
|
|
|
1651
2090
|
this.applyChangesToComponent(
|
|
1652
2091
|
new_raw_text,
|
|
1653
|
-
this.
|
|
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.
|
|
2141
|
+
this.props.mimeType,
|
|
1703
2142
|
this.enableHtml()
|
|
1704
2143
|
);
|
|
1705
2144
|
|
|
1706
2145
|
this.applyChangesToComponent(
|
|
1707
2146
|
new_raw_text,
|
|
1708
|
-
this.
|
|
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.
|
|
2185
|
+
this.props.mimeType,
|
|
1747
2186
|
this.enableHtml()
|
|
1748
2187
|
);
|
|
1749
2188
|
this.applyChangesToComponent(
|
|
1750
2189
|
new_raw_text,
|
|
1751
|
-
this.
|
|
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.
|
|
2235
|
+
this.props.mimeType,
|
|
1794
2236
|
this.enableHtml()
|
|
1795
2237
|
);
|
|
1796
2238
|
this.applyChangesToComponent(
|
|
1797
2239
|
new_raw_text,
|
|
1798
|
-
this.
|
|
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) {
|