@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.
@@ -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", "varPhi", "theta",
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
- const LATEX_SYMBOLS_DICT = toDict(LATEX_SYMBOLS)
489
- const NORMAL_SYMBOLS_DICT = toDict(NORMAL_SYMBOLS)
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
- const tagName = node.tagName.toLowerCase()
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 (node) {
83
- let textNode = findFirstTextNode(node)
84
- return isSmallSpace(textNode)
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 === null || mimeType === undefined || mimeType === "")
474
- mimeType = determineMimeType(string)
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
- return null
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
- return rangeParams
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 start node with index "+params.endNodeIndex)
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
+ }