@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
|
@@ -214,7 +214,8 @@ const LATEX_SYMBOLS = [
|
|
|
214
214
|
{name:"Double integral",latex:"\\iint_{#}^{#}",buttonLatex:"\\iint",tooltip:"Double integral",moveCursor:-2},
|
|
215
215
|
{name:"Triple integral",latex:"\\iiint_{#}^{#}",buttonLatex:"\\iiint",tooltip:"Double integral",moveCursor:-2},
|
|
216
216
|
{name:"Contour integral",latex:"\\oint_{#}^{#}",buttonLatex:"\\oint",tooltip:"Contour integral",moveCursor:-2},
|
|
217
|
-
|
|
217
|
+
{name:"Surface integral",latex:"\\oiint_{#}^{#}",buttonLatex:"\\oiint",tooltip:"Surface integral",moveCursor:-2},
|
|
218
|
+
{name:"Volume integral",latex:"\\oiiint_{#}^{#}",buttonLatex:"\\oiiint",tooltip:"Volume integral",moveCursor:-2},
|
|
218
219
|
{name:"Integral a b",latex:"\\int_{#}^{#}",buttonLatex:"\\int_{a}^{b}",tooltip:"Integral",moveCursor:-2,scale:90},
|
|
219
220
|
{name:"Double integral a b",latex:"\\iint_{#}^{#}",buttonLatex:"\\iint_{a}^{b}",tooltip:"Double integral",moveCursor:-2},
|
|
220
221
|
{name:"Triple integral a b",latex:"\\iiint_{#}^{#}",buttonLatex:"\\iiint_{a}^{b}",tooltip:"Double integral",moveCursor:-2},
|
|
@@ -229,7 +230,8 @@ const LATEX_SYMBOLS = [
|
|
|
229
230
|
{name:"Box",latex:"\\square",buttonLatex:"\\square",tooltip:"Box"},
|
|
230
231
|
{name:"Plus",latex:"+",buttonLatex:"+",tooltip:"Plus"},
|
|
231
232
|
{name:"Minus",latex:"-",buttonLatex:"-",tooltip:"Mius"},
|
|
232
|
-
{name:"Plus or minus",latex:"\\pm",buttonLatex:"\\pm",tooltip:"Plus or minus"},
|
|
233
|
+
{name: "Plus or minus", latex: "\\pm", buttonLatex: "\\pm", tooltip: "Plus or minus"},
|
|
234
|
+
{name:"Minus or plus",latex:"\\mp",buttonLatex:"\\mp",tooltip:"Minus or plus"},
|
|
233
235
|
{name:"Times",latex:"\\times",buttonLatex:"\\times",tooltip:"Times"},
|
|
234
236
|
{name:"Divide",latex:"\\div",buttonLatex:"\\div",tooltip:"Divide"},
|
|
235
237
|
{name:"Center dot",latex:"\\cdot",buttonLatex:"\\cdot",tooltip:"Center dot"},
|
|
@@ -366,7 +368,7 @@ const BUTTON_AREAS = {
|
|
|
366
368
|
"zeta", "eta", "theta", "iota",
|
|
367
369
|
"kappa", "lambda", "mu", "nu",
|
|
368
370
|
"xi", "omicron", "pi", "rho", "sigma", "tau",
|
|
369
|
-
"upsilon", "phi", "chi", "psi", "omega"],
|
|
371
|
+
"upsilon", "phi", "varphi", "chi", "psi", "omega"],
|
|
370
372
|
"GreekUpper": ["Gamma", "Delta", "Theta", "Lambda",
|
|
371
373
|
"Xi", "Pi", "Sigma", "Phi", "Psi", "Omega","nabla"],
|
|
372
374
|
"Sets": ["cup", "cap", "in", "notin",
|
|
@@ -403,13 +405,13 @@ const BUTTON_AREAS = {
|
|
|
403
405
|
"Physics2": ["Plus","Minus","Plus or minus","Times","Equals",
|
|
404
406
|
"Approximately", "Proportionate"],
|
|
405
407
|
"Physics3": ["alpha","varepsilon", "eta", "gamma","lambda", "omega", "mu", "nu",
|
|
406
|
-
"pi", "rho", "sigma", "phi", "
|
|
408
|
+
"pi", "rho", "sigma", "phi", "Phi", "theta",
|
|
407
409
|
"overline{v_e}","overline{v_\\mu}",
|
|
408
410
|
"Subscript zero", "Subscript max", "Subscript min", "Subscript rms"],
|
|
409
411
|
"Expert": ["langle","rangle",
|
|
410
|
-
"Double integral", "Triple integral", "Contour integral",
|
|
412
|
+
"Double integral", "Triple integral", "Contour integral", "Surface integral", "Volume integral",
|
|
411
413
|
"Double dot", "Widehat","Check", "Tilde","Mathring",
|
|
412
|
-
"Right arrow with text","rightleftharpoons"],
|
|
414
|
+
"Right arrow with text","rightleftharpoons", "Minus or plus"],
|
|
413
415
|
"Matrices": ["1-vector", "1x2 matrix","1x3 matrix",
|
|
414
416
|
"2-vector", "2x2 matrix", "2x3 matrix",
|
|
415
417
|
"3-vector", "3x2 matrix","3x3 matrix",
|
|
@@ -485,10 +487,17 @@ function toDict(symbols) {
|
|
|
485
487
|
return dict;
|
|
486
488
|
}
|
|
487
489
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
+
let LATEX_SYMBOLS_DICT = null;
|
|
491
|
+
let NORMAL_SYMBOLS_DICT = null;
|
|
492
|
+
|
|
493
|
+
function initialise() {
|
|
494
|
+
LATEX_SYMBOLS_DICT = toDict(LATEX_SYMBOLS)
|
|
495
|
+
NORMAL_SYMBOLS_DICT = toDict(NORMAL_SYMBOLS)
|
|
496
|
+
}
|
|
490
497
|
|
|
491
498
|
function getSymbolData(name) {
|
|
499
|
+
if (LATEX_SYMBOLS_DICT === null)
|
|
500
|
+
initialise();
|
|
492
501
|
if (name in LATEX_SYMBOLS_DICT)
|
|
493
502
|
return LATEX_SYMBOLS_DICT[name]
|
|
494
503
|
|
|
@@ -42,6 +42,10 @@ const MIME_TYPE_TEX = "application/x-tex"
|
|
|
42
42
|
const MIME_TYPE_LATEX = "application/x-latex"
|
|
43
43
|
const MIME_TYPE_ZZISH_HTML_MATH = "application/x-zzish-html-math"
|
|
44
44
|
|
|
45
|
+
export function getMark() {
|
|
46
|
+
return MARK
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
export function isKatexNode(node) {
|
|
46
50
|
return node.className === "katex" || node.className === "katex-error"
|
|
47
51
|
}
|
|
@@ -54,10 +58,14 @@ function isBIUNode(node) {
|
|
|
54
58
|
if (node.nodeType !== 1)
|
|
55
59
|
return false
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
const tagName = node.tagName.toLowerCase();
|
|
62
|
+
if (tagName === "span")
|
|
63
|
+
return !isKatexNode(node)
|
|
58
64
|
return false ||
|
|
59
65
|
tagName === "b" ||
|
|
66
|
+
tagName === "strong" ||
|
|
60
67
|
tagName === "i" ||
|
|
68
|
+
tagName === "em" ||
|
|
61
69
|
tagName === "u" ||
|
|
62
70
|
tagName === "sup" ||
|
|
63
71
|
tagName === "sub" ||
|
|
@@ -70,6 +78,14 @@ function isBrNode(node) {
|
|
|
70
78
|
return node.tagName.toLowerCase() === "br"
|
|
71
79
|
}
|
|
72
80
|
|
|
81
|
+
function nodeStartsWithSmallSpace(node) {
|
|
82
|
+
if (node.nodeType === 3 &&
|
|
83
|
+
node.nodeValue.length >= 1 &&
|
|
84
|
+
node.nodeValue.charCodeAt(0) === SMALL_SPACE_CHAR_CODE)
|
|
85
|
+
return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
function isSmallSpace(node) {
|
|
74
90
|
if(node.nodeType === 3 &&
|
|
75
91
|
node.nodeValue.length === 1 &&
|
|
@@ -79,10 +95,9 @@ function isSmallSpace(node) {
|
|
|
79
95
|
return false
|
|
80
96
|
}
|
|
81
97
|
|
|
82
|
-
export function startsWithSmallSpace (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
export function startsWithSmallSpace (editableDiv) {
|
|
99
|
+
let textNode = findFirstTextNode(editableDiv);
|
|
100
|
+
return isSmallSpace(textNode);
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
export function countNodeTypes(node, counter=null) {
|
|
@@ -201,6 +216,13 @@ function getFirstDescendentByTagName(node, tagName) {
|
|
|
201
216
|
return null
|
|
202
217
|
}
|
|
203
218
|
|
|
219
|
+
function logAllDescendants(node) {
|
|
220
|
+
for (let i = 0; i < node.childNodes.length; i++) {
|
|
221
|
+
logAllDescendants(node.childNodes[i]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
204
226
|
export function getLatex(node) {
|
|
205
227
|
try {
|
|
206
228
|
// Example:
|
|
@@ -323,8 +345,6 @@ function addTextForNode(node, arrayOfText, nodeToMark=null, offset=0, useHtmlMat
|
|
|
323
345
|
addText(arrayOfText,
|
|
324
346
|
useHtmlMath ? encodeToHtml(node.nodeValue) : encodeToLatex(node.nodeValue),
|
|
325
347
|
mark, offset, useHtmlMath)
|
|
326
|
-
} else if (isBrNode(node)) {
|
|
327
|
-
useHtmlMath && arrayOfText.push("<br>")
|
|
328
348
|
} else if (isBIUNode(node)) {
|
|
329
349
|
useHtmlMath && arrayOfText.push("<"+node.nodeName+">")
|
|
330
350
|
for (let i = 0; i < node.childNodes.length; i++) {
|
|
@@ -468,10 +488,11 @@ function renderLatexString (katex, s, t) {
|
|
|
468
488
|
export function rawTextToHtml(katex, string, mimeType) {
|
|
469
489
|
|
|
470
490
|
if (string === "")
|
|
471
|
-
return ""
|
|
491
|
+
return "<p>"+SMALL_SPACE+"</p>"
|
|
472
492
|
|
|
473
|
-
if (mimeType
|
|
474
|
-
|
|
493
|
+
if (mimeType == null || mimeType === "") {
|
|
494
|
+
mimeType = determineMimeType(string);
|
|
495
|
+
}
|
|
475
496
|
|
|
476
497
|
if (mimeType === MIME_TYPE_PLAIN_TEXT)
|
|
477
498
|
return encodeToHtml(string)
|
|
@@ -651,7 +672,91 @@ export function findNodeWithIndex(node, index, indexCounter=null) {
|
|
|
651
672
|
}
|
|
652
673
|
}
|
|
653
674
|
|
|
654
|
-
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function findGlobalOffset(node, nodeToFind, offset, result=null) {
|
|
679
|
+
// console.log(node.nodeType+":"+node.nodeValue+" === "+ nodeToFind.nodeType+":"+nodeToFind.nodeValue)
|
|
680
|
+
// console.log(node === nodeToFind)
|
|
681
|
+
if (result === null || result === undefined)
|
|
682
|
+
throw error
|
|
683
|
+
if (node === nodeToFind) {
|
|
684
|
+
result.offset = result.offset + offset
|
|
685
|
+
// console.log("result.offset now: "+result.offset)
|
|
686
|
+
if (isTextNode(node) && nodeStartsWithSmallSpace(node)) {
|
|
687
|
+
if (offset > 0)
|
|
688
|
+
result.offset = result.offset - 1
|
|
689
|
+
}
|
|
690
|
+
return true
|
|
691
|
+
}
|
|
692
|
+
if (isKatexNode(node)) {
|
|
693
|
+
if (node === nodeToFind || node.contains(nodeToFind))
|
|
694
|
+
return true
|
|
695
|
+
else {
|
|
696
|
+
result.offset++
|
|
697
|
+
return false
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (isTextNode(node)) {
|
|
701
|
+
result.offset += node.nodeValue.length
|
|
702
|
+
if (nodeStartsWithSmallSpace(node))
|
|
703
|
+
result.offset -= 1
|
|
704
|
+
return false
|
|
705
|
+
}
|
|
706
|
+
const childNodes = node.childNodes
|
|
707
|
+
if (childNodes === null || childNodes === undefined)
|
|
708
|
+
return false
|
|
709
|
+
|
|
710
|
+
for (let i = 0; i < childNodes.length; i++) {
|
|
711
|
+
let child = childNodes[i]
|
|
712
|
+
// console.log("Checking child:"+child.nodeType+":"+child.tagName+":"+child.nodeValue)
|
|
713
|
+
let found = findGlobalOffset(child, nodeToFind, offset, result)
|
|
714
|
+
if (found)
|
|
715
|
+
return true
|
|
716
|
+
}
|
|
717
|
+
if (node.nodeType === 1 && node.tagName === "P") {
|
|
718
|
+
result.offset ++
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function findNodeAndOffsetForGlobalOffset(node, globalOffset, result=null) {
|
|
725
|
+
if (isKatexNode(node)) {
|
|
726
|
+
result.node = null;
|
|
727
|
+
result.offset++;
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
if (isTextNode(node)) {
|
|
731
|
+
result.node = node
|
|
732
|
+
result.offset += node.nodeValue.length
|
|
733
|
+
if (nodeStartsWithSmallSpace(node))
|
|
734
|
+
result.offset -= 1
|
|
735
|
+
// console.log("result.offset now: "+result.offset)
|
|
736
|
+
return false
|
|
737
|
+
}
|
|
738
|
+
const childNodes = node.childNodes
|
|
739
|
+
if (childNodes === null || childNodes === undefined)
|
|
740
|
+
return false
|
|
741
|
+
|
|
742
|
+
for (let i = 0; i < childNodes.length; i++) {
|
|
743
|
+
let found = findNodeAndOffsetForGlobalOffset(childNodes[i], globalOffset, result)
|
|
744
|
+
if (found)
|
|
745
|
+
return true
|
|
746
|
+
|
|
747
|
+
if (result.node !== null && result.node !== undefined) {
|
|
748
|
+
if (result.offset >= globalOffset) {
|
|
749
|
+
result.offset = globalOffset-(result.offset-result.node.nodeValue.length)
|
|
750
|
+
return true
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (node.nodeType === 1 && node.tagName === "P") {
|
|
756
|
+
result.node = null
|
|
757
|
+
result.offset ++
|
|
758
|
+
}
|
|
759
|
+
return false;
|
|
655
760
|
}
|
|
656
761
|
|
|
657
762
|
export function findFirstTextNode(node) {
|
|
@@ -668,6 +773,7 @@ export function findFirstTextNode(node) {
|
|
|
668
773
|
return null
|
|
669
774
|
}
|
|
670
775
|
|
|
776
|
+
// https://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
|
|
671
777
|
export function getRangeParams(editableDiv) {
|
|
672
778
|
// console.log ("getRangeParams")
|
|
673
779
|
// console.log("Inner html: "+editableDiv.innerHTML)
|
|
@@ -684,6 +790,32 @@ export function getRangeParams(editableDiv) {
|
|
|
684
790
|
}
|
|
685
791
|
|
|
686
792
|
const range = selection.getRangeAt(0);
|
|
793
|
+
// logAllDescendants(editableDiv)
|
|
794
|
+
// If the range is not within a text node, move it to a text node
|
|
795
|
+
// This typically happens when the end of a range is at the start of a new line
|
|
796
|
+
if (range.startContainer.nodeType !== 3) {
|
|
797
|
+
let textNode = findFirstTextNode(range.startContainer)
|
|
798
|
+
if (textNode !== null) {
|
|
799
|
+
console.warn("Adjusting start pos in getRangeParams as container was of type: "+range.startContainer.nodeName)
|
|
800
|
+
range.setStart(textNode,0)
|
|
801
|
+
} else {
|
|
802
|
+
console.error("Unable to getRangeParams. seletion.startContainer is not a text node. "+
|
|
803
|
+
"Node type: "+range.startContainer.nodeName)
|
|
804
|
+
return null
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (range.endContainer.nodeType !== 3) {
|
|
808
|
+
let textNode = findFirstTextNode(range.endContainer)
|
|
809
|
+
if (textNode !== null) {
|
|
810
|
+
console.warn("Adjusting end pos in getRangeParams as container was of type: "+range.endContainer.nodeName)
|
|
811
|
+
range.setEnd(textNode,0)
|
|
812
|
+
} else {
|
|
813
|
+
console.error("Unable to getRangeParams. seletion.endContainer is not a text node. "+
|
|
814
|
+
"Node type: "+range.endContainer.nodeName)
|
|
815
|
+
return null
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// Find the index of the start and end nodes
|
|
687
819
|
|
|
688
820
|
let startNodeIndex = findIndexOfNode(editableDiv, range.startContainer)
|
|
689
821
|
let endNodeIndex = findIndexOfNode(editableDiv, range.endContainer)
|
|
@@ -693,15 +825,72 @@ export function getRangeParams(editableDiv) {
|
|
|
693
825
|
// console.log("Start node: |"+range.startContainer.nodeValue+"|")
|
|
694
826
|
// console.log("Start node index found: "+startNodeIndex)
|
|
695
827
|
// console.log("Start node offset: "+range.startOffset)
|
|
828
|
+
|
|
829
|
+
// Find the global offset of the start and end nodes
|
|
830
|
+
let startResult = {offset:0}
|
|
831
|
+
findGlobalOffset(editableDiv, range.startContainer, range.startOffset, startResult)
|
|
832
|
+
// console.log("Global offset of start: "+startResult.offset)
|
|
833
|
+
let endResult = {offset:0}
|
|
834
|
+
findGlobalOffset(editableDiv, range.endContainer, range.endOffset, endResult)
|
|
835
|
+
// console.log("Global offset of end: "+endResult.offset)
|
|
696
836
|
|
|
697
837
|
let rangeParams = {
|
|
698
838
|
startNodeIndex: startNodeIndex,
|
|
699
839
|
startOffset: range.startOffset,
|
|
700
840
|
endNodeIndex: endNodeIndex,
|
|
701
|
-
endOffset: range.endOffset
|
|
841
|
+
endOffset: range.endOffset,
|
|
842
|
+
startGlobalOffset: startResult.offset,
|
|
843
|
+
endGlobalOffset: endResult.offset
|
|
702
844
|
}
|
|
703
845
|
|
|
704
|
-
|
|
846
|
+
return rangeParams;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function setRangeParamsFromGlobalOffsets(editableDiv, params) {
|
|
850
|
+
let new_range = null;
|
|
851
|
+
try {
|
|
852
|
+
if (params === null || params === undefined)
|
|
853
|
+
throw new Error("Can't set range params: params === "+params)
|
|
854
|
+
let startResult = {offset:0}
|
|
855
|
+
findNodeAndOffsetForGlobalOffset(editableDiv, params.startGlobalOffset, startResult)
|
|
856
|
+
// console.log(startResult.node.nodeType+":"+startResult.node.tagName+":"+
|
|
857
|
+
// startResult.node.nodeValue+":"+startResult.offset)
|
|
858
|
+
let endResult = {offset:0}
|
|
859
|
+
findNodeAndOffsetForGlobalOffset(editableDiv, params.endGlobalOffset, endResult)
|
|
860
|
+
// console.log(endResult.node.nodeType+":"+endResult.node.tagName+":"+
|
|
861
|
+
// endResult.node.nodeValue+":"+endResult.offset)
|
|
862
|
+
if (!startResult.node || !endResult.node ||
|
|
863
|
+
!startResult.offset || !endResult.offset) {
|
|
864
|
+
console.log("Can't set range with start and end results")
|
|
865
|
+
console.log(startResult)
|
|
866
|
+
console.log(endResult)
|
|
867
|
+
throw new Error("Could not extract valid nodes and offsets from params")
|
|
868
|
+
}
|
|
869
|
+
if (startResult.offset === 0 &&
|
|
870
|
+
startResult.node.nodeValue.length > 0 &&
|
|
871
|
+
nodeStartsWithSmallSpace(startResult.node)) {
|
|
872
|
+
startResult.offset = 1
|
|
873
|
+
}
|
|
874
|
+
if (endResult.offset === 0 &&
|
|
875
|
+
endResult.node.nodeValue.length > 0 &&
|
|
876
|
+
nodeStartsWithSmallSpace(endResult.node)) {
|
|
877
|
+
endResult.offset = 1
|
|
878
|
+
}
|
|
879
|
+
// console.log("Setting range to:")
|
|
880
|
+
// console.log(startResult.node.nodeType+":"+startResult.node.tagName+":"+
|
|
881
|
+
// startResult.node.nodeValue+":"+startResult.offset)
|
|
882
|
+
new_range = document.createRange();
|
|
883
|
+
new_range.setStart(startResult.node, startResult.offset);
|
|
884
|
+
new_range.setEnd(endResult.node, endResult.offset);
|
|
885
|
+
const selection = window.getSelection();
|
|
886
|
+
selection.removeAllRanges()
|
|
887
|
+
selection.addRange(new_range)
|
|
888
|
+
} catch (error) {
|
|
889
|
+
console.error(error)
|
|
890
|
+
console.log("Trying to set range:")
|
|
891
|
+
console.log(new_range)
|
|
892
|
+
console.log(editableDiv.childNodes)
|
|
893
|
+
}
|
|
705
894
|
}
|
|
706
895
|
|
|
707
896
|
export function setRangeParams(editableDiv, params) {
|
|
@@ -714,6 +903,12 @@ export function setRangeParams(editableDiv, params) {
|
|
|
714
903
|
console.error("Can't set range params: params === "+params)
|
|
715
904
|
return
|
|
716
905
|
}
|
|
906
|
+
|
|
907
|
+
if (params.startGlobalOffset) {
|
|
908
|
+
setRangeParamsFromGlobalOffsets(editableDiv, params)
|
|
909
|
+
return
|
|
910
|
+
}
|
|
911
|
+
|
|
717
912
|
if (params.startNodeIndex === null || params.startNodeIndex === undefined) {
|
|
718
913
|
console.error("Can't set range params: params.startNodeIndex === "+params.startNodeIndex)
|
|
719
914
|
return
|
|
@@ -785,7 +980,7 @@ export function setRangeParams(editableDiv, params) {
|
|
|
785
980
|
}
|
|
786
981
|
|
|
787
982
|
if (endNode === null) {
|
|
788
|
-
console.error("Could not set range to
|
|
983
|
+
console.error("Could not set range to end node with index "+params.endNodeIndex)
|
|
789
984
|
endNode = findFirstTextNode(editableDiv)
|
|
790
985
|
endOffset = 0
|
|
791
986
|
}
|
|
@@ -803,6 +998,13 @@ export function setRangeParams(editableDiv, params) {
|
|
|
803
998
|
|
|
804
999
|
new_range = document.createRange();
|
|
805
1000
|
new_range.setStart(startNode, startOffset);
|
|
1001
|
+
if (endOffset > endNode.length) {
|
|
1002
|
+
console.warn("endOffset > endNode.length when setting range: "+
|
|
1003
|
+
endOffset+ " > "+endNode.length+
|
|
1004
|
+
" - "+endNode.nodeType+
|
|
1005
|
+
" - "+endNode.nodeValue)
|
|
1006
|
+
endOffset = endNode.length
|
|
1007
|
+
}
|
|
806
1008
|
new_range.setEnd(endNode, endOffset);
|
|
807
1009
|
|
|
808
1010
|
// console.log("Setting range")
|
|
@@ -835,3 +1037,155 @@ export function stripScripts(s) {
|
|
|
835
1037
|
|
|
836
1038
|
return s
|
|
837
1039
|
}
|
|
1040
|
+
|
|
1041
|
+
function sanitiseHtmlForElement(element) {
|
|
1042
|
+
if (isTextNode(element))
|
|
1043
|
+
return document.createTextNode(element.nodeValue)
|
|
1044
|
+
if (isKatexNode(element)) {
|
|
1045
|
+
// console.log("Copying katex node")
|
|
1046
|
+
let clone = element.cloneNode(true)
|
|
1047
|
+
return clone
|
|
1048
|
+
}
|
|
1049
|
+
if (element.tagName === "HEAD" ||
|
|
1050
|
+
element.tagName === "STYLE" ||
|
|
1051
|
+
element.tagName === "LINK")
|
|
1052
|
+
return null
|
|
1053
|
+
let new_element = null
|
|
1054
|
+
if (isBIUNode(element)) {
|
|
1055
|
+
// console.log("Updating descendent: "+element.textContent)
|
|
1056
|
+
new_element = document.createElement(element.tagName);
|
|
1057
|
+
} else if (element.nodeType === 1 &&
|
|
1058
|
+
(
|
|
1059
|
+
element.tagName === "LI" ||
|
|
1060
|
+
element.tagName === "DT" ||
|
|
1061
|
+
element.tagName === "DD" ||
|
|
1062
|
+
element.tagName === "THEAD" ||
|
|
1063
|
+
element.tagName === "TR" ||
|
|
1064
|
+
element.tagName === "H1" ||
|
|
1065
|
+
element.tagName === "H2" ||
|
|
1066
|
+
element.tagName === "H3" ||
|
|
1067
|
+
element.tagName === "H4" ||
|
|
1068
|
+
element.tagName === "H5" ||
|
|
1069
|
+
element.tagName === "H6"
|
|
1070
|
+
)) {
|
|
1071
|
+
// console.log("Converting descendent: <"+element.tagName+">"+element.textContent)
|
|
1072
|
+
new_element = document.createElement('p');
|
|
1073
|
+
} else {
|
|
1074
|
+
// console.log("Stripping descendent: <"+element.tagName+">"+element.textContent)
|
|
1075
|
+
new_element = document.createElement('span');
|
|
1076
|
+
}
|
|
1077
|
+
if (element.hasChildNodes()) {
|
|
1078
|
+
for (let i = 0; i < element.childNodes.length; i++) {
|
|
1079
|
+
let new_child = sanitiseHtmlForElement(element.childNodes[i])
|
|
1080
|
+
if (new_child !== null)
|
|
1081
|
+
new_element.appendChild(new_child)
|
|
1082
|
+
if (element.childNodes[i].tagName === "TH" || element.childNodes[i].tagName === "TD")
|
|
1083
|
+
new_element.appendChild(document.createTextNode(" "))
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return new_element
|
|
1087
|
+
}
|
|
1088
|
+
const SPAN_TAG = new RegExp("<span>|</span>","g")
|
|
1089
|
+
// const BR_TAG_REG_EXP = new RegExp("<br[\s\S]*?>","g")
|
|
1090
|
+
// const COMMENT_TAG_REG_EXP = new RegExp("<!--.+?-->","g")
|
|
1091
|
+
// const COMMENT_TAG_REG_EXP = new RegExp("(?=<!--([\s\S]*?)-->","g")
|
|
1092
|
+
// const COMMENT_TAG_REG_EXP = new RegExp("<!--[\s\S]*?-->","gm")
|
|
1093
|
+
// const MULTI_WHITE_SPACE_REG_EXP = new RegExp("\s{2,}","gm")
|
|
1094
|
+
const KATEX_START_HTML = "<span class=\"katex\""
|
|
1095
|
+
const KATEX_END_HTML = "</span></span></span></span>"
|
|
1096
|
+
export function sanitiseHtml(s) {
|
|
1097
|
+
// Remove all <br> tags (as some of these can be inside katex tags)
|
|
1098
|
+
s = s.replace(/<br[\s\S]*?>/gm,"")
|
|
1099
|
+
// console.log("After removing comments:")
|
|
1100
|
+
// console.log(s)
|
|
1101
|
+
// Next, convert all elements to supported elements
|
|
1102
|
+
var div = document.createElement('div');
|
|
1103
|
+
div.innerHTML = s;
|
|
1104
|
+
let sanitisedElement = sanitiseHtmlForElement(div)
|
|
1105
|
+
let sanitised = sanitisedElement.innerHTML
|
|
1106
|
+
// Next, remove all span tags, except those for katex span nodes
|
|
1107
|
+
// NB: A simpler solution may have been to create something other than
|
|
1108
|
+
// span tags in the sanitiseHtmlForElement(div) method and then simply
|
|
1109
|
+
// remove all span tags.
|
|
1110
|
+
let i = 0
|
|
1111
|
+
let j = 0
|
|
1112
|
+
let its = 0
|
|
1113
|
+
let output = []
|
|
1114
|
+
do {
|
|
1115
|
+
its++
|
|
1116
|
+
j = sanitised.indexOf(KATEX_START_HTML,i)
|
|
1117
|
+
if (j < 0) {
|
|
1118
|
+
let part = sanitised.substring(i,sanitised.length)
|
|
1119
|
+
part = part.replace(SPAN_TAG,"")
|
|
1120
|
+
output.push(part)
|
|
1121
|
+
break
|
|
1122
|
+
}
|
|
1123
|
+
let part = sanitised.substring(i,j)
|
|
1124
|
+
part = part.replace(SPAN_TAG,"")
|
|
1125
|
+
output.push(part)
|
|
1126
|
+
let k = sanitised.indexOf(KATEX_END_HTML, j) + KATEX_END_HTML.length
|
|
1127
|
+
if (k < 0) {
|
|
1128
|
+
console.warn("Unable to find closing katex tag")
|
|
1129
|
+
k = sanitised.length
|
|
1130
|
+
}
|
|
1131
|
+
output.push(sanitised.substring(j,k))
|
|
1132
|
+
i = k
|
|
1133
|
+
|
|
1134
|
+
} while (j > 0 && its < 20)
|
|
1135
|
+
|
|
1136
|
+
sanitised = output.join("")
|
|
1137
|
+
// Remove new lines
|
|
1138
|
+
sanitised = sanitised.replace(/(\r\n|\r|\n)/gm, "") // new line
|
|
1139
|
+
// Convert any multispaces to a single space
|
|
1140
|
+
sanitised = sanitised.replace(/\s{2,}/gm, " ") // 2 or more white spaces
|
|
1141
|
+
// Next, make sure that each line of html is wrapped in paragraph tags
|
|
1142
|
+
i = 0
|
|
1143
|
+
j = 0
|
|
1144
|
+
its = 0
|
|
1145
|
+
output = []
|
|
1146
|
+
do {
|
|
1147
|
+
its++
|
|
1148
|
+
j = sanitised.indexOf("</p>",i)
|
|
1149
|
+
if (j < 0) {
|
|
1150
|
+
output.push(sanitised.substring(i,sanitised.length))
|
|
1151
|
+
break
|
|
1152
|
+
}
|
|
1153
|
+
j = j+4 // Move j four characters to end of </p> tag
|
|
1154
|
+
output.push(sanitised.substring(i,j)) // copy as far as </p>
|
|
1155
|
+
// if we have reached the end of the string, finish
|
|
1156
|
+
if (j === sanitised.length)
|
|
1157
|
+
break
|
|
1158
|
+
// if not enough characters for a <p> then can't be a <p>
|
|
1159
|
+
if (j+3 > sanitised.length) {
|
|
1160
|
+
output.push("<p>")
|
|
1161
|
+
output.push(sanitised.substring(j,sanitised.length))
|
|
1162
|
+
output.push("</p>")
|
|
1163
|
+
break
|
|
1164
|
+
}
|
|
1165
|
+
// If the next characters are the start of a paragraph tag then continue
|
|
1166
|
+
let nextChars = sanitised.substring(j,j+3)
|
|
1167
|
+
if (nextChars === "<p>") {
|
|
1168
|
+
i = j
|
|
1169
|
+
continue
|
|
1170
|
+
}
|
|
1171
|
+
// If the next characters are the start of a katex tag then continue
|
|
1172
|
+
if (j+KATEX_START_HTML.length < sanitised.length) {
|
|
1173
|
+
nextChars = sanitised.substring(j,j+KATEX_START_HTML.length)
|
|
1174
|
+
if (nextChars === KATEX_START_HTML) {
|
|
1175
|
+
i = j
|
|
1176
|
+
continue
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
// Ok! We have found some html that is not wrapped in a paragraph tag
|
|
1180
|
+
// Lets insert the paragraph tags
|
|
1181
|
+
output.push("<p>")
|
|
1182
|
+
let k = sanitised.indexOf("<p>",j)
|
|
1183
|
+
if (k < 0)
|
|
1184
|
+
k = sanitised.length
|
|
1185
|
+
output.push(sanitised.substring(j,k))
|
|
1186
|
+
output.push("</p>")
|
|
1187
|
+
i = k
|
|
1188
|
+
} while (j > 0 && its < 20)
|
|
1189
|
+
sanitised = output.join("")
|
|
1190
|
+
return sanitised
|
|
1191
|
+
}
|