@zzish/math-rich-input 0.1.48 → 0.1.49

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.
@@ -1,11 +1,12 @@
1
- import { determineMimeType } from './mimeTypeHelper'
1
+ import { determineMimeType } from "./mimeTypeHelper";
2
2
 
3
- export const RAW_TEXT_MATH_START_TAG_TEX = "\\["
4
- export const RAW_TEXT_MATH_END_TAG_TEX = "\\]"
5
- export const RAW_TEXT_MATH_START_TAG_HTML_MATH = "<math>"
6
- export const RAW_TEXT_MATH_END_TAG_HTML_MATH = "</math>"
3
+ export const RAW_TEXT_MATH_START_TAG_TEX = "\\[";
4
+ export const RAW_TEXT_MATH_END_TAG_TEX = "\\]";
5
+ export const RAW_TEXT_MATH_START_TAG_HTML_MATH = "<math>";
6
+ export const RAW_TEXT_MATH_END_TAG_HTML_MATH = "</math>";
7
7
 
8
- const INLINE_TEX_REG_EXP = /\$\$[\s\S]+?\$\$|\\\[[\s\S]+?\\\]|\\\([\s\S]+?\\\)|\$[^$\\]*(?:\\.[^$\\]*)*\$/g;
8
+ const INLINE_TEX_REG_EXP =
9
+ /\$\$[\s\S]+?\$\$|\\\[[\s\S]+?\\\]|\\\([\s\S]+?\\\)|\$[^$\\]*(?:\\.[^$\\]*)*\$/g;
9
10
  const BLOCK_TEX_REG_EXP = /\$\$[\s\S]+?\$\$|\\\[[\s\S]+?\\\]/g;
10
11
 
11
12
  // const INLINE_TEX_REG_EXP = /(?<!\\)\$[\s\S]+?(?<!\\)\$/g
@@ -14,54 +15,52 @@ const BLOCK_TEX_REG_EXP = /\$\$[\s\S]+?\$\$|\\\[[\s\S]+?\\\]/g;
14
15
 
15
16
  // const INLINE_TEX_REG_EXP = /(?<!\\)(?:((?<!\$)\${1,2}(?!\$))|(\\\()|(\\\[)|(\\begin\{equation\}))(?(1)(.*?)(?<!\\)(?<!\$)\1(?!\$)|(?:(.*(?R)?.*)(?<!\\)(?:(?(2)\\\)|(?(3)\\\]|\\end\{equation\})))))/
16
17
 
17
-
18
18
  // const HTML_MATH_REG_EXP = /<math>.*?<\/math>/gi;
19
19
  const HTML_MATH_REG_EXP = /<math>[\s\S]*?<\/math>/gi;
20
20
 
21
- const SPAN_OPEN_BASE = "<span class=\"base\">"
22
- const SPAN_CLOSE = "</span>"
21
+ const SPAN_OPEN_BASE = '<span class="base">';
22
+ const SPAN_CLOSE = "</span>";
23
23
 
24
- const MARK = String.fromCharCode(0)
24
+ const MARK = String.fromCharCode(0);
25
25
  // const MARK = "_M_A_R_K_"
26
- const MARK_REG_EXP = new RegExp(MARK, "g")
26
+ const MARK_REG_EXP = new RegExp(MARK, "g");
27
27
 
28
- const TEMP_CHAR = String.fromCharCode(1)
29
- const TEMP_CHAR_REG_EXP = new RegExp(TEMP_CHAR, "g")
30
- const DOLLAR_SLASH_REG_EXP = /\\\$/g
28
+ const TEMP_CHAR = String.fromCharCode(1);
29
+ const TEMP_CHAR_REG_EXP = new RegExp(TEMP_CHAR, "g");
30
+ const DOLLAR_SLASH_REG_EXP = /\\\$/g;
31
31
 
32
- const SMALL_SPACE_CHAR_CODE = 8203
33
- const SMALL_SPACE = String.fromCharCode(SMALL_SPACE_CHAR_CODE)
34
- const SMALL_SPACE_REG_EXP = new RegExp(SMALL_SPACE, "g")
32
+ const SMALL_SPACE_CHAR_CODE = 8203;
33
+ const SMALL_SPACE = String.fromCharCode(SMALL_SPACE_CHAR_CODE);
34
+ const SMALL_SPACE_REG_EXP = new RegExp(SMALL_SPACE, "g");
35
35
 
36
- const P_TAG_REG_EXP = new RegExp("<[p|P]>", "g")
37
- const P_ELEMENT_SMALL_SPACE = "<p>" + SMALL_SPACE
36
+ const P_TAG_REG_EXP = new RegExp("<[p|P]>", "g");
37
+ const P_ELEMENT_SMALL_SPACE = "<p>" + SMALL_SPACE;
38
38
 
39
- const MIME_TYPE_PLAIN_TEXT = "text/plain"
40
- const MIME_TYPE_HTML = "text/html"
41
- const MIME_TYPE_TEX = "application/x-tex"
42
- const MIME_TYPE_LATEX = "application/x-latex"
43
- const MIME_TYPE_ZZISH_HTML_MATH = "application/x-zzish-html-math"
39
+ const MIME_TYPE_PLAIN_TEXT = "text/plain";
40
+ const MIME_TYPE_HTML = "text/html";
41
+ const MIME_TYPE_TEX = "application/x-tex";
42
+ const MIME_TYPE_LATEX = "application/x-latex";
43
+ const MIME_TYPE_ZZISH_HTML_MATH = "application/x-zzish-html-math";
44
44
 
45
45
  export function getMark() {
46
- return MARK
46
+ return MARK;
47
47
  }
48
48
 
49
49
  export function isKatexNode(node) {
50
- return node.className === "katex" || node.className === "katex-error"
50
+ return node.className === "katex" || node.className === "katex-error";
51
51
  }
52
52
 
53
53
  function isTextNode(node) {
54
- return node.nodeType === 3
54
+ return node.nodeType === 3;
55
55
  }
56
56
 
57
57
  function isBIUNode(node) {
58
- if (node.nodeType !== 1)
59
- return false
58
+ if (node.nodeType !== 1) return false;
60
59
 
61
60
  const tagName = node.tagName.toLowerCase();
62
- if (tagName === "span")
63
- return !isKatexNode(node)
64
- return false ||
61
+ if (tagName === "span") return !isKatexNode(node);
62
+ return (
63
+ false ||
65
64
  tagName === "b" ||
66
65
  tagName === "strong" ||
67
66
  tagName === "i" ||
@@ -70,131 +69,131 @@ function isBIUNode(node) {
70
69
  tagName === "sup" ||
71
70
  tagName === "sub" ||
72
71
  tagName === "p"
72
+ );
73
73
  }
74
74
 
75
75
  function isBrNode(node) {
76
- if (node.tagName === undefined)
77
- return false
78
- return node.tagName.toLowerCase() === "br"
76
+ if (node.tagName === undefined) return false;
77
+ return node.tagName.toLowerCase() === "br";
79
78
  }
80
79
 
81
80
  function nodeStartsWithSmallSpace(node) {
82
- if (node.nodeType === 3 &&
81
+ if (
82
+ node.nodeType === 3 &&
83
83
  node.nodeValue.length >= 1 &&
84
- node.nodeValue.charCodeAt(0) === SMALL_SPACE_CHAR_CODE)
84
+ node.nodeValue.charCodeAt(0) === SMALL_SPACE_CHAR_CODE
85
+ )
85
86
  return true;
86
87
  return false;
87
88
  }
88
89
 
89
90
  function isSmallSpace(node) {
90
- if(node.nodeType === 3 &&
91
+ if (
92
+ node.nodeType === 3 &&
91
93
  node.nodeValue.length === 1 &&
92
- node.nodeValue.charCodeAt(0) === SMALL_SPACE_CHAR_CODE)
93
- return true
94
+ node.nodeValue.charCodeAt(0) === SMALL_SPACE_CHAR_CODE
95
+ )
96
+ return true;
94
97
 
95
- return false
98
+ return false;
96
99
  }
97
100
 
98
- export function startsWithSmallSpace (editableDiv) {
101
+ export function startsWithSmallSpace(editableDiv) {
99
102
  let textNode = findFirstTextNode(editableDiv);
100
103
  return isSmallSpace(textNode);
101
104
  }
102
105
 
103
- export function countNodeTypes(node, counter=null) {
106
+ export function countNodeTypes(node, counter = null) {
104
107
  if (counter === null)
105
108
  counter = {
106
- math:0,
107
- html:0,
108
- text:0
109
- }
109
+ math: 0,
110
+ html: 0,
111
+ text: 0,
112
+ };
110
113
 
111
114
  if (node == null || node === undefined) {
112
- console.error("Node: "+node)
113
- return counter
115
+ console.error("Node: " + node);
116
+ return counter;
114
117
  }
115
118
 
116
- if (isKatexNode(node))
117
- counter.math++
118
- else if (isBIUNode(node))
119
- counter.html++
120
- else if (isTextNode(node))
121
- counter.text++
119
+ if (isKatexNode(node)) counter.math++;
120
+ else if (isBIUNode(node)) counter.html++;
121
+ else if (isTextNode(node)) counter.text++;
122
122
 
123
- if (isKatexNode(node))
124
- return counter
123
+ if (isKatexNode(node)) return counter;
125
124
 
126
- if (node.childNodes === null || node.childNodes === undefined)
127
- return counter
125
+ if (node.childNodes === null || node.childNodes === undefined) return counter;
128
126
 
129
127
  for (let i = 0; i < node.childNodes.length; i++)
130
- countNodeTypes(node.childNodes[i], counter)
128
+ countNodeTypes(node.childNodes[i], counter);
131
129
 
132
- return counter
130
+ return counter;
133
131
  }
134
132
 
135
133
  export function getNodeText(node) {
136
134
  // console.log("getNodeText")
137
135
  // console.log(node)
138
136
 
139
- if (node.nodeType === 3)
140
- return node.nodeValue
137
+ if (node.nodeType === 3) return node.nodeValue;
141
138
  else if (node.nodeType === 1) {
142
- node = findFirstTextNode(node)
143
- return node.nodeValue
139
+ node = findFirstTextNode(node);
140
+ return node.nodeValue;
144
141
  } else {
145
- console.error("Trying to get length of node that is not text or BUI: ")
146
- console.log(node)
147
- return null
142
+ console.error("Trying to get length of node that is not text or BUI: ");
143
+ console.log(node);
144
+ return null;
148
145
  }
149
146
  }
150
147
 
151
148
  export function getNodeTextLength(node) {
152
- let text = getNodeText(node)
153
- if (text === null || text === undefined)
154
- return -1
149
+ let text = getNodeText(node);
150
+ if (text === null || text === undefined) return -1;
155
151
 
156
- return text.length
152
+ return text.length;
157
153
  }
158
154
 
159
155
  export function removeMarks(raw_text) {
160
- return raw_text.replace(MARK_REG_EXP,"")
156
+ return raw_text.replace(MARK_REG_EXP, "");
161
157
  }
162
158
 
163
159
  export function getCharacterBeforeMarks(text) {
164
- let start = text.indexOf(MARK)
160
+ let start = text.indexOf(MARK);
165
161
  if (start < 1) {
166
- console.warn("Can't get character before mark. Start: "+start)
167
- return null
162
+ console.warn("Can't get character before mark. Start: " + start);
163
+ return null;
168
164
  }
169
- return text.charAt(start-1)
165
+ return text.charAt(start - 1);
170
166
  }
171
167
 
172
168
  export function replaceCharacterBeforeMarks(text, character) {
173
- let start = text.indexOf(MARK)
169
+ let start = text.indexOf(MARK);
174
170
  if (start < 1) {
175
- console.warn("Can't replace character before mark. Start: "+start)
176
- return text
171
+ console.warn("Can't replace character before mark. Start: " + start);
172
+ return text;
177
173
  }
178
- return text.substring(0, start-1) + character + text.substring(start)
174
+ return text.substring(0, start - 1) + character + text.substring(start);
179
175
  }
180
176
 
181
177
  export function replaceStringBeforeMarks(text, oldString, newString) {
182
- let start = text.indexOf(MARK)
178
+ let start = text.indexOf(MARK);
183
179
  if (start < 1) {
184
- console.warn("Can't replace character before mark. Start: "+start)
185
- return text
180
+ console.warn("Can't replace character before mark. Start: " + start);
181
+ return text;
186
182
  }
187
- return text.substring(0, start-oldString.length) + newString + text.substring(start)
183
+ return (
184
+ text.substring(0, start - oldString.length) +
185
+ newString +
186
+ text.substring(start)
187
+ );
188
188
  }
189
189
 
190
-
191
190
  export function insertCharacterBeforeMarks(text, character) {
192
- let start = text.indexOf(MARK)
191
+ let start = text.indexOf(MARK);
193
192
  if (start < 0) {
194
- console.warn("Can't replace character before mark. Start: "+start)
195
- return text
193
+ console.warn("Can't replace character before mark. Start: " + start);
194
+ return text;
196
195
  }
197
- return text.substring(0, start) + character + text.substring(start)
196
+ return text.substring(0, start) + character + text.substring(start);
198
197
  }
199
198
 
200
199
  // export function replaceCharacterAfterMarks(text, character) {
@@ -204,16 +203,14 @@ export function insertCharacterBeforeMarks(text, character) {
204
203
  // }
205
204
 
206
205
  function getFirstDescendentByTagName(node, tagName) {
207
- if (node.tagName === tagName)
208
- return node
206
+ if (node.tagName === tagName) return node;
209
207
 
210
208
  if (node.childNodes !== null && node.childNodes !== undefined)
211
209
  for (let i = 0; i < node.childNodes.length; i++) {
212
- let result = getFirstDescendentByTagName(node.childNodes[i],tagName)
213
- if (result !== null)
214
- return result
210
+ let result = getFirstDescendentByTagName(node.childNodes[i], tagName);
211
+ if (result !== null) return result;
215
212
  }
216
- return null
213
+ return null;
217
214
  }
218
215
 
219
216
  function logAllDescendants(node) {
@@ -222,54 +219,69 @@ function logAllDescendants(node) {
222
219
  }
223
220
  }
224
221
 
225
-
226
222
  export function getLatex(node) {
227
223
  try {
228
224
  // Example:
229
225
  // <annotation encoding="application/x-tex">L' = {L}{\sqrt{1-\frac{v^2}{c^2}}}</annotation>
230
- let annotation = getFirstDescendentByTagName(node, "annotation")
231
- let latex = annotation.childNodes[0].nodeValue
232
- return latex
226
+ let annotation = getFirstDescendentByTagName(node, "annotation");
227
+
228
+ // Defensive checks for corrupt KaTeX nodes
229
+ if (!annotation) {
230
+ console.warn("KaTeX node missing annotation element");
231
+ return "error";
232
+ }
233
+
234
+ if (!annotation.childNodes || annotation.childNodes.length === 0) {
235
+ console.warn("KaTeX annotation element has no child nodes");
236
+ return "error";
237
+ }
238
+
239
+ if (!annotation.childNodes[0] || !annotation.childNodes[0].nodeValue) {
240
+ console.warn("KaTeX annotation missing text content");
241
+ return "error";
242
+ }
243
+
244
+ let latex = annotation.childNodes[0].nodeValue;
245
+ return latex;
233
246
  } catch (error) {
234
- console.error(error)
235
- console.log(node)
236
- return "error"
247
+ console.error("Error extracting LaTeX from KaTeX node:", error);
248
+ console.log("Problematic node:", node);
249
+ return "error";
237
250
  }
238
251
  }
239
252
 
240
-
241
253
  function encodeToHtml(text) {
242
- text = text.replace(/&/g,"&amp;")
243
- text = text.replace(/>/g,"&gt;")
244
- text = text.replace(/</g,"&lt;")
245
- text = text.replace(/'/g,"&apos;")
246
- text = text.replace(/"/g,"&quot;")
247
- text = text.replace(/\$/g,"&#36;")
248
- text = text.replace(/,/g,"&#44;")
249
- text = text.replace(/:/g,"&#58;")
250
- text = text.replace(/\|/g,"&#124;")
251
- return text
254
+ text = text.replace(/&/g, "&amp;");
255
+ text = text.replace(/>/g, "&gt;");
256
+ text = text.replace(/</g, "&lt;");
257
+ text = text.replace(/'/g, "&apos;");
258
+ text = text.replace(/"/g, "&quot;");
259
+ text = text.replace(/\$/g, "&#36;");
260
+ text = text.replace(/,/g, "&#44;");
261
+ text = text.replace(/:/g, "&#58;");
262
+ text = text.replace(/\|/g, "&#124;");
263
+ return text;
252
264
  }
253
265
 
254
- var decodeFromHtml = (function() {
266
+ var decodeFromHtml = (function () {
255
267
  var translate_re = /&(#36|quot|apos|lt|gt|amp|#44|#58|#124);/g,
256
268
  translate = {
257
269
  // 'nbsp': String.fromCharCode(160),
258
- '#36': '$',
259
- 'quot': '"',
260
- 'apos': '\'',
261
- 'lt' : '<',
262
- 'gt' : '>',
263
- 'amp' : '&',
264
- '#44' : ',',
265
- '#58' : ':',
266
- '#124' : '|'
270
+ "#36": "$",
271
+ quot: '"',
272
+ apos: "'",
273
+ lt: "<",
274
+ gt: ">",
275
+ amp: "&",
276
+ "#44": ",",
277
+ "#58": ":",
278
+ "#124": "|",
267
279
  },
268
- translator = function($0, $1) {
280
+ translator = function ($0, $1) {
269
281
  return translate[$1];
270
282
  };
271
283
 
272
- return function(s) {
284
+ return function (s) {
273
285
  // return s
274
286
  return s.replace(translate_re, translator);
275
287
  };
@@ -288,95 +300,120 @@ var decodeFromHtml = (function() {
288
300
  // Note we don't need to encode it so do nothing here
289
301
  function encodeToLatex(text) {
290
302
  // console.log("Pre-encoded: "+text)
291
- text = text.replace(/\$/g,"\\$")
303
+ text = text.replace(/\$/g, "\\$");
292
304
  // console.log("Post-encoded: "+text)
293
- return text
305
+ return text;
294
306
  }
295
307
 
296
308
  // Note we don't need to decode it so do nothing here
297
309
  function decodeFromLatex(text) {
298
310
  // console.log("Pre-decoded: "+text)
299
- text = text.replace(/\\\$/g,"$")
311
+ text = text.replace(/\\\$/g, "$");
300
312
  // console.log("Post-decoded: "+text)
301
- return text
313
+ return text;
302
314
  }
303
315
 
304
316
  export function removeEncodingIfPlainText(text, mimeType, useHtmlMath) {
305
- if (mimeType !== MIME_TYPE_PLAIN_TEXT)
306
- return text
317
+ if (mimeType !== MIME_TYPE_PLAIN_TEXT) return text;
307
318
 
308
- if (useHtmlMath)
309
- return decodeFromHtml(text)
310
- else
311
- return decodeFromLatex(text)
319
+ if (useHtmlMath) return decodeFromHtml(text);
320
+ else return decodeFromLatex(text);
312
321
  }
313
322
 
314
- function addText(arrayOfText, text, mark, offset, useHtmlMath=true) {
323
+ function addText(arrayOfText, text, mark, offset, useHtmlMath = true) {
315
324
  // console.log("Mark: "+mark)
316
325
  if (offset === 0) {
317
326
  if (mark) {
318
- arrayOfText.push(MARK)
319
- arrayOfText.push(MARK)
327
+ arrayOfText.push(MARK);
328
+ arrayOfText.push(MARK);
320
329
  }
321
- arrayOfText.push(text)
330
+ arrayOfText.push(text);
322
331
  } else {
323
332
  if (useHtmlMath) {
324
- let decoded = decodeFromHtml(text)
325
- arrayOfText.push(encodeToHtml(decoded.substring(0,offset)))
333
+ let decoded = decodeFromHtml(text);
334
+ arrayOfText.push(encodeToHtml(decoded.substring(0, offset)));
326
335
  if (mark) {
327
- arrayOfText.push(MARK)
328
- arrayOfText.push(MARK)
336
+ arrayOfText.push(MARK);
337
+ arrayOfText.push(MARK);
329
338
  }
330
- arrayOfText.push(encodeToHtml(decoded.substring(offset)))
339
+ arrayOfText.push(encodeToHtml(decoded.substring(offset)));
331
340
  } else {
332
- let decoded = decodeFromLatex(text)
333
- arrayOfText.push(encodeToLatex(decoded.substring(0,offset)))
341
+ let decoded = decodeFromLatex(text);
342
+ arrayOfText.push(encodeToLatex(decoded.substring(0, offset)));
334
343
  if (mark) {
335
- arrayOfText.push(MARK)
336
- arrayOfText.push(MARK)
344
+ arrayOfText.push(MARK);
345
+ arrayOfText.push(MARK);
337
346
  }
338
- arrayOfText.push(encodeToLatex(decoded.substring(offset)))
347
+ arrayOfText.push(encodeToLatex(decoded.substring(offset)));
339
348
  }
340
349
  }
341
350
  }
342
351
 
343
- function addTextForNode(node, arrayOfText, nodeToMark=null, offset=0, useHtmlMath=true) {
344
- let mark = false
345
- if (node === nodeToMark)
346
- mark = true
352
+ function addTextForNode(
353
+ node,
354
+ arrayOfText,
355
+ nodeToMark = null,
356
+ offset = 0,
357
+ useHtmlMath = true
358
+ ) {
359
+ let mark = false;
360
+ if (node === nodeToMark) mark = true;
347
361
  // console.log("Node: "+node+" "+mark)
348
362
 
349
363
  try {
350
364
  if (isTextNode(node)) {
351
- addText(arrayOfText,
352
- useHtmlMath ? encodeToHtml(node.nodeValue) : encodeToLatex(node.nodeValue),
353
- mark, offset, useHtmlMath)
365
+ addText(
366
+ arrayOfText,
367
+ useHtmlMath
368
+ ? encodeToHtml(node.nodeValue)
369
+ : encodeToLatex(node.nodeValue),
370
+ mark,
371
+ offset,
372
+ useHtmlMath
373
+ );
354
374
  } else if (isBIUNode(node)) {
355
- useHtmlMath && arrayOfText.push("<"+node.nodeName+">")
375
+ useHtmlMath && arrayOfText.push("<" + node.nodeName + ">");
356
376
  for (let i = 0; i < node.childNodes.length; i++) {
357
- addTextForNode(node.childNodes[i], arrayOfText, nodeToMark, offset, useHtmlMath)
377
+ addTextForNode(
378
+ node.childNodes[i],
379
+ arrayOfText,
380
+ nodeToMark,
381
+ offset,
382
+ useHtmlMath
383
+ );
358
384
  }
359
- useHtmlMath && arrayOfText.push("</"+node.nodeName+">")
385
+ useHtmlMath && arrayOfText.push("</" + node.nodeName + ">");
360
386
  } else if (isKatexNode(node)) {
361
- if (mark)
362
- arrayOfText.push(MARK)
363
- arrayOfText.push(useHtmlMath ? RAW_TEXT_MATH_START_TAG_HTML_MATH : RAW_TEXT_MATH_START_TAG_TEX)
364
- arrayOfText.push(getLatex(node))
365
- arrayOfText.push(useHtmlMath ? RAW_TEXT_MATH_END_TAG_HTML_MATH : RAW_TEXT_MATH_END_TAG_TEX)
366
- if (mark)
367
- arrayOfText.push(MARK)
387
+ if (mark) arrayOfText.push(MARK);
388
+ arrayOfText.push(
389
+ useHtmlMath
390
+ ? RAW_TEXT_MATH_START_TAG_HTML_MATH
391
+ : RAW_TEXT_MATH_START_TAG_TEX
392
+ );
393
+ arrayOfText.push(getLatex(node));
394
+ arrayOfText.push(
395
+ useHtmlMath
396
+ ? RAW_TEXT_MATH_END_TAG_HTML_MATH
397
+ : RAW_TEXT_MATH_END_TAG_TEX
398
+ );
399
+ if (mark) arrayOfText.push(MARK);
400
+ } else if (isBrNode(node)) {
401
+ // Handle BR tags as line breaks
402
+ if (mark) arrayOfText.push(MARK);
403
+ arrayOfText.push("\n");
404
+ if (mark) arrayOfText.push(MARK);
368
405
  } else {
369
- console.error("Can't render node in editable div: Unknown node type")
370
- console.log(node)
371
- console.log("Node.type:"+node.type)
372
- console.log("Node.tagName:"+node.tagName)
406
+ console.error("Can't render node in editable div: Unknown node type");
407
+ console.log(node);
408
+ console.log("Node.type:" + node.type);
409
+ console.log("Node.tagName:" + node.tagName);
373
410
  }
374
411
  } catch (error) {
375
- console.error(error)
376
- console.log("Node: ")
377
- console.log(node)
378
- console.log("Node.type:"+node.type)
379
- console.log("Node.tagName:"+node.tagName)
412
+ console.error(error);
413
+ console.log("Node: ");
414
+ console.log(node);
415
+ console.log("Node.type:" + node.type);
416
+ console.log("Node.tagName:" + node.tagName);
380
417
  }
381
418
  }
382
419
 
@@ -387,66 +424,87 @@ function addTextForNode(node, arrayOfText, nodeToMark=null, offset=0, useHtmlMat
387
424
  //
388
425
  // Marked nodes are useful if a node may need to be deleted and allows the node to be
389
426
  // deleted by stripping it out of the raw text.
390
- export function elementToMarkedRawText(element, nodeToMark=null, offset=0, useHtmlMath=true) {
427
+ export function elementToMarkedRawText(
428
+ element,
429
+ nodeToMark = null,
430
+ offset = 0,
431
+ useHtmlMath = true
432
+ ) {
391
433
  // console.log("elementToMarkedRawText")
392
434
  // console.log(element.innerHTML)
393
- if (element.innerHTML === "<p><br></p>")
394
- throw new Error ("We are here")
435
+
436
+ // Handle empty content cases gracefully
437
+ if (
438
+ element.innerHTML === "<p><br></p>" ||
439
+ element.innerHTML === "<br>" ||
440
+ element.innerHTML === ""
441
+ ) {
442
+ return "";
443
+ }
444
+
395
445
  // console.log("Node to mark:")
396
446
  // console.log(nodeToMark)
397
- let nodes = element.childNodes
447
+ let nodes = element.childNodes;
398
448
  // console.log(nodes)
399
449
 
400
- let result = []
450
+ let result = [];
401
451
 
402
- if ((nodes === null || nodes === undefined || nodes.length === 0) && nodeToMark !== null) {
452
+ if (
453
+ (nodes === null || nodes === undefined || nodes.length === 0) &&
454
+ nodeToMark !== null
455
+ ) {
403
456
  // console.log("Empty field")
404
- addText(result, "", true, 0)
457
+ addText(result, "", true, 0);
405
458
  } else {
406
459
  for (let i = 0; i < nodes.length; i++)
407
- addTextForNode(nodes[i], result, nodeToMark, offset, useHtmlMath)
460
+ addTextForNode(nodes[i], result, nodeToMark, offset, useHtmlMath);
408
461
  }
409
- let raw_text = result.join("")
462
+ let raw_text = result.join("");
410
463
 
411
464
  // remove any zero-width spaces we added to the html
412
- raw_text = raw_text.replace(SMALL_SPACE_REG_EXP,'')
465
+ raw_text = raw_text.replace(SMALL_SPACE_REG_EXP, "");
413
466
 
414
- return raw_text
467
+ return raw_text;
415
468
  }
416
469
 
417
- export function replaceMarksWithMath(markedRawText, latex, useHtmlMath=true) {
418
- let start_pos = markedRawText.indexOf(MARK)
419
- let end_pos = markedRawText.indexOf(MARK, start_pos + MARK.length)
420
- let new_raw_text = markedRawText.substring(0,start_pos) +
421
- (useHtmlMath ? RAW_TEXT_MATH_START_TAG_HTML_MATH : RAW_TEXT_MATH_START_TAG_TEX) +
470
+ export function replaceMarksWithMath(markedRawText, latex, useHtmlMath = true) {
471
+ let start_pos = markedRawText.indexOf(MARK);
472
+ let end_pos = markedRawText.indexOf(MARK, start_pos + MARK.length);
473
+ let new_raw_text =
474
+ markedRawText.substring(0, start_pos) +
475
+ (useHtmlMath
476
+ ? RAW_TEXT_MATH_START_TAG_HTML_MATH
477
+ : RAW_TEXT_MATH_START_TAG_TEX) +
422
478
  latex +
423
- (useHtmlMath ? RAW_TEXT_MATH_END_TAG_HTML_MATH : RAW_TEXT_MATH_END_TAG_TEX) +
424
- markedRawText.substring(end_pos + MARK.length)
425
- return new_raw_text
479
+ (useHtmlMath
480
+ ? RAW_TEXT_MATH_END_TAG_HTML_MATH
481
+ : RAW_TEXT_MATH_END_TAG_TEX) +
482
+ markedRawText.substring(end_pos + MARK.length);
483
+ return new_raw_text;
426
484
  }
427
485
 
428
- function stripDollars (stringToStrip) {
486
+ function stripDollars(stringToStrip) {
429
487
  return stringToStrip[0] === "$" && stringToStrip[1] !== "$"
430
488
  ? stringToStrip.slice(1, -1)
431
- : stringToStrip.slice(2, -2)
489
+ : stringToStrip.slice(2, -2);
432
490
  }
433
491
 
434
- function stripTags (stringToStrip) {
435
- let i = stringToStrip.indexOf(">")
436
- let j = stringToStrip.lastIndexOf("<")
492
+ function stripTags(stringToStrip) {
493
+ let i = stringToStrip.indexOf(">");
494
+ let j = stringToStrip.lastIndexOf("<");
437
495
  if (i <= 0 || j <= 0) {
438
- console.error("Could not strip tag from string: "+stringToStrip)
439
- return stringToStrip
496
+ console.error("Could not strip tag from string: " + stringToStrip);
497
+ return stringToStrip;
440
498
  }
441
499
 
442
- return stringToStrip.substring(i+1,j)
500
+ return stringToStrip.substring(i + 1, j);
443
501
  }
444
502
 
445
- function getDisplayType (stringToDisplay) {
446
- return stringToDisplay.match(BLOCK_TEX_REG_EXP) ? "block" : "inline"
503
+ function getDisplayType(stringToDisplay) {
504
+ return stringToDisplay.match(BLOCK_TEX_REG_EXP) ? "block" : "inline";
447
505
  }
448
506
 
449
- function renderLatexString (katex, s, t) {
507
+ function renderLatexString(katex, s, t) {
450
508
  let options = {
451
509
  children: "",
452
510
  displayMode: false,
@@ -459,8 +517,8 @@ function renderLatexString (katex, s, t) {
459
517
  minRuleThickness: 0,
460
518
  colorIsTextColor: false,
461
519
  strict: "warn",
462
- trust: false
463
- }
520
+ trust: false,
521
+ };
464
522
 
465
523
  let renderedString;
466
524
  try {
@@ -476,355 +534,368 @@ function renderLatexString (katex, s, t) {
476
534
 
477
535
  // Reduce multiple base class spans to one single span
478
536
  try {
479
- let fixed_renderedString = renderedString
480
- let i = fixed_renderedString.indexOf(SPAN_OPEN_BASE)
537
+ let fixed_renderedString = renderedString;
538
+ let i = fixed_renderedString.indexOf(SPAN_OPEN_BASE);
481
539
  do {
482
- i = fixed_renderedString.indexOf(SPAN_OPEN_BASE, i + SPAN_OPEN_BASE.length)
483
- if (i < 0)
484
- break;
485
- let j = fixed_renderedString.lastIndexOf(SPAN_CLOSE, i)
540
+ i = fixed_renderedString.indexOf(
541
+ SPAN_OPEN_BASE,
542
+ i + SPAN_OPEN_BASE.length
543
+ );
544
+ if (i < 0) break;
545
+ let j = fixed_renderedString.lastIndexOf(SPAN_CLOSE, i);
486
546
  fixed_renderedString =
487
- fixed_renderedString.substring(0,j) +
488
- fixed_renderedString.substring(i+SPAN_OPEN_BASE.length)
489
- } while (i > 0)
490
- renderedString = fixed_renderedString
547
+ fixed_renderedString.substring(0, j) +
548
+ fixed_renderedString.substring(i + SPAN_OPEN_BASE.length);
549
+ } while (i > 0);
550
+ renderedString = fixed_renderedString;
491
551
  } catch (error) {
492
- console.log(error)
552
+ console.log(error);
493
553
  }
494
554
 
495
555
  return renderedString;
496
- };
556
+ }
497
557
 
498
558
  export function rawTextToHtml(katex, string, mimeType) {
499
-
500
- if (string === "")
501
- return "<p>"+SMALL_SPACE+"</p>"
559
+ if (string === "") return "<p>" + SMALL_SPACE + "</p>";
502
560
 
503
561
  if (mimeType == null || mimeType === "") {
504
562
  mimeType = determineMimeType(string);
505
563
  }
506
564
 
507
- if (mimeType === MIME_TYPE_PLAIN_TEXT)
508
- return encodeToHtml(string)
565
+ if (mimeType === MIME_TYPE_PLAIN_TEXT) return encodeToHtml(string);
509
566
 
510
- let parseAsTex
511
- if (mimeType === MIME_TYPE_TEX ||
512
- mimeType === MIME_TYPE_LATEX)
513
- parseAsTex = true
514
- else if (mimeType === MIME_TYPE_HTML ||
515
- mimeType === MIME_TYPE_ZZISH_HTML_MATH)
516
- parseAsTex = false
567
+ let parseAsTex;
568
+ if (mimeType === MIME_TYPE_TEX || mimeType === MIME_TYPE_LATEX)
569
+ parseAsTex = true;
570
+ else if (
571
+ mimeType === MIME_TYPE_HTML ||
572
+ mimeType === MIME_TYPE_ZZISH_HTML_MATH
573
+ )
574
+ parseAsTex = false;
517
575
  else {
518
- parseAsTex = false
519
- console.error("Unknown mime type: "+mimeType)
576
+ parseAsTex = false;
577
+ console.error("Unknown mime type: " + mimeType);
520
578
  }
521
579
 
522
580
  // console.log("Original string: "+string)
523
- if (parseAsTex)
524
- string = string.replace(DOLLAR_SLASH_REG_EXP, TEMP_CHAR)
581
+ if (parseAsTex) string = string.replace(DOLLAR_SLASH_REG_EXP, TEMP_CHAR);
525
582
  // console.log("Modded string: "+string)
526
583
 
527
- const stringElements = string.split(parseAsTex ? INLINE_TEX_REG_EXP : HTML_MATH_REG_EXP);
584
+ const stringElements = string.split(
585
+ parseAsTex ? INLINE_TEX_REG_EXP : HTML_MATH_REG_EXP
586
+ );
528
587
 
529
588
  if (parseAsTex) {
530
- for (let i = 0; i <stringElements.length; i++) {
531
- stringElements[i] = stringElements[i].replace(TEMP_CHAR_REG_EXP, "$")
532
- stringElements[i] = encodeToHtml(stringElements[i])
589
+ for (let i = 0; i < stringElements.length; i++) {
590
+ stringElements[i] = stringElements[i].replace(TEMP_CHAR_REG_EXP, "$");
591
+ stringElements[i] = encodeToHtml(stringElements[i]);
533
592
  }
534
593
  } else {
535
594
  // Insert small space at end of <p></p> elements so that empty lines get rendered
536
- for (let i = 0; i <stringElements.length; i++)
537
- stringElements[i] = stringElements[i].replace(P_TAG_REG_EXP, P_ELEMENT_SMALL_SPACE)
595
+ for (let i = 0; i < stringElements.length; i++)
596
+ stringElements[i] = stringElements[i].replace(
597
+ P_TAG_REG_EXP,
598
+ P_ELEMENT_SMALL_SPACE
599
+ );
538
600
  }
539
601
 
540
602
  // Insert small space at start of first element if it is an empty element
541
- if (stringElements[0] === "") // This means the original string stars with latex
542
- stringElements[0] = "&#" + SMALL_SPACE.charCodeAt(0)
603
+ if (stringElements[0] === "")
604
+ // This means the original string stars with latex
605
+ stringElements[0] = "&#" + SMALL_SPACE.charCodeAt(0);
543
606
 
544
- if (stringElements.length === 1)
545
- return stringElements[0]
607
+ if (stringElements.length === 1) return stringElements[0];
546
608
 
547
- const latexElements = string.match(parseAsTex ? INLINE_TEX_REG_EXP : HTML_MATH_REG_EXP);
609
+ const latexElements = string.match(
610
+ parseAsTex ? INLINE_TEX_REG_EXP : HTML_MATH_REG_EXP
611
+ );
548
612
 
549
613
  const result = [];
550
614
  for (let i = 0; i < stringElements.length; i++) {
551
- result.push(stringElements[i])
615
+ result.push(stringElements[i]);
552
616
 
553
617
  if (latexElements[i]) {
554
- let element = latexElements[i]
555
- let type
618
+ let element = latexElements[i];
619
+ let type;
556
620
  if (parseAsTex) {
557
- type = getDisplayType(element)
558
- element = stripDollars(element)
559
- element = element.replace(TEMP_CHAR_REG_EXP, "\\$")
621
+ type = getDisplayType(element);
622
+ element = stripDollars(element);
623
+ element = element.replace(TEMP_CHAR_REG_EXP, "\\$");
560
624
  } else {
561
- type = "inline"
562
- element = stripTags(element)
625
+ type = "inline";
626
+ element = stripTags(element);
563
627
  }
564
628
 
565
- let renderedElement = renderLatexString(katex, element, type)
566
- renderedElement = renderedElement + "&#"+SMALL_SPACE.charCodeAt(0)
629
+ let renderedElement = renderLatexString(katex, element, type);
630
+ renderedElement = renderedElement + "&#" + SMALL_SPACE.charCodeAt(0);
567
631
 
568
- result.push(renderedElement)
632
+ result.push(renderedElement);
569
633
  }
570
634
  }
571
635
 
572
- return result.join("")
636
+ return result.join("");
573
637
  }
574
638
 
575
639
  export function isEmptyNode(node) {
576
- return node.childNodes === null || node.childNodes === undefined || node.childNodes.length === 0
640
+ return (
641
+ node.childNodes === null ||
642
+ node.childNodes === undefined ||
643
+ node.childNodes.length === 0
644
+ );
577
645
  }
578
646
 
579
- export function findIndexOfNode(node, nodeToFind, indexCounter=null) {
647
+ export function findIndexOfNode(node, nodeToFind, indexCounter = null) {
580
648
  if (indexCounter === null) {
581
649
  // Starting with root node
582
- if (node === nodeToFind)
583
- return 0
584
- indexCounter = {count:0}
650
+ if (node === nodeToFind) return 0;
651
+ indexCounter = { count: 0 };
585
652
  } else {
586
653
  if (isKatexNode(node)) {
587
654
  if (node === nodeToFind || node.contains(nodeToFind))
588
- return indexCounter.count
655
+ return indexCounter.count;
589
656
  else {
590
- indexCounter.count ++
591
- return -1
657
+ indexCounter.count++;
658
+ return -1;
592
659
  }
593
660
  }
594
661
 
595
662
  if (isBrNode(node)) {
596
- if (node === nodeToFind)
597
- return indexCounter.count
663
+ if (node === nodeToFind) return indexCounter.count;
598
664
  else {
599
- const nextSibling = node.nextSibling
600
- if (nextSibling !== null && nextSibling !== undefined && isBrNode(nextSibling)) {
601
- indexCounter.count ++
602
- return -1
665
+ const nextSibling = node.nextSibling;
666
+ if (
667
+ nextSibling !== null &&
668
+ nextSibling !== undefined &&
669
+ isBrNode(nextSibling)
670
+ ) {
671
+ indexCounter.count++;
672
+ return -1;
603
673
  } else {
604
- return -1
674
+ return -1;
605
675
  }
606
-
607
676
  }
608
677
  }
609
678
 
610
679
  if (isTextNode(node)) {
611
- if (node === nodeToFind)
612
- return indexCounter.count
680
+ if (node === nodeToFind) return indexCounter.count;
613
681
 
614
- indexCounter.count ++
682
+ indexCounter.count++;
615
683
  }
616
-
617
684
  }
618
685
 
619
- const childNodes = node.childNodes
620
- if (childNodes === null || childNodes === undefined)
621
- return -1
686
+ const childNodes = node.childNodes;
687
+ if (childNodes === null || childNodes === undefined) return -1;
622
688
 
623
689
  for (let i = 0; i < childNodes.length; i++) {
624
- let index = findIndexOfNode(childNodes[i], nodeToFind, indexCounter)
625
- if (index >= 0)
626
- return index
690
+ let index = findIndexOfNode(childNodes[i], nodeToFind, indexCounter);
691
+ if (index >= 0) return index;
627
692
  }
628
693
 
629
- return -1
694
+ return -1;
630
695
  }
631
696
 
632
- export function findNodeWithIndex(node, index, indexCounter=null) {
697
+ export function findNodeWithIndex(node, index, indexCounter = null) {
633
698
  if (indexCounter === null) {
634
699
  // Starting with root node
635
700
  if (index === 0 && isEmptyNode(node)) {
636
701
  // console.log("Empty node")
637
- return node
702
+ return node;
638
703
  }
639
- indexCounter = {count:0}
704
+ indexCounter = { count: 0 };
640
705
  } else {
641
706
  if (isKatexNode(node)) {
642
- if (index === indexCounter.count)
643
- return node
707
+ if (index === indexCounter.count) return node;
644
708
  else {
645
- indexCounter.count ++
646
- return null
709
+ indexCounter.count++;
710
+ return null;
647
711
  }
648
712
  }
649
713
 
650
714
  if (isBrNode(node)) {
651
- if (index === indexCounter.count)
652
- return node
715
+ if (index === indexCounter.count) return node;
653
716
  else {
654
- const nextSibling = node.nextSibling
655
- if (nextSibling !== null && nextSibling !== undefined && isBrNode(nextSibling)) {
656
- indexCounter.count ++
657
- return null
717
+ const nextSibling = node.nextSibling;
718
+ if (
719
+ nextSibling !== null &&
720
+ nextSibling !== undefined &&
721
+ isBrNode(nextSibling)
722
+ ) {
723
+ indexCounter.count++;
724
+ return null;
658
725
  } else {
659
- return null
726
+ return null;
660
727
  }
661
-
662
728
  }
663
729
  }
664
730
 
665
731
  // If not the root node then
666
732
  if (isTextNode(node)) {
667
- if (index === indexCounter.count)
668
- return node
733
+ if (index === indexCounter.count) return node;
669
734
 
670
- indexCounter.count ++
735
+ indexCounter.count++;
671
736
  }
672
737
  }
673
738
 
674
- const childNodes = node.childNodes
675
- if (childNodes === null || childNodes === undefined)
676
- return null
739
+ const childNodes = node.childNodes;
740
+ if (childNodes === null || childNodes === undefined) return null;
677
741
 
678
742
  for (let i = 0; i < childNodes.length; i++) {
679
- let found_node = findNodeWithIndex(childNodes[i], index, indexCounter)
743
+ let found_node = findNodeWithIndex(childNodes[i], index, indexCounter);
680
744
  if (found_node !== null) {
681
- return found_node
745
+ return found_node;
682
746
  }
683
747
  }
684
748
 
685
749
  return null;
686
750
  }
687
751
 
688
- function findGlobalOffset(node, nodeToFind, offset, result=null) {
752
+ function findGlobalOffset(node, nodeToFind, offset, result = null) {
689
753
  // console.log(node.nodeType+":"+node.nodeValue+" === "+ nodeToFind.nodeType+":"+nodeToFind.nodeValue)
690
754
  // console.log(node === nodeToFind)
691
- if (result === null || result === undefined)
692
- throw error
755
+ if (result === null || result === undefined) throw error;
693
756
  if (node === nodeToFind) {
694
- result.offset = result.offset + offset
757
+ result.offset = result.offset + offset;
695
758
  // console.log("result.offset now: "+result.offset)
696
759
  if (isTextNode(node) && nodeStartsWithSmallSpace(node)) {
697
- if (offset > 0)
698
- result.offset = result.offset - 1
760
+ if (offset > 0) result.offset = result.offset - 1;
699
761
  }
700
- return true
762
+ return true;
701
763
  }
702
764
  if (isKatexNode(node)) {
703
- if (node === nodeToFind || node.contains(nodeToFind))
704
- return true
765
+ if (node === nodeToFind || node.contains(nodeToFind)) return true;
705
766
  else {
706
- result.offset++
707
- return false
767
+ result.offset++;
768
+ return false;
708
769
  }
709
770
  }
710
771
  if (isTextNode(node)) {
711
- result.offset += node.nodeValue.length
712
- if (nodeStartsWithSmallSpace(node))
713
- result.offset -= 1
714
- return false
772
+ result.offset += node.nodeValue.length;
773
+ if (nodeStartsWithSmallSpace(node)) result.offset -= 1;
774
+ return false;
715
775
  }
716
- const childNodes = node.childNodes
717
- if (childNodes === null || childNodes === undefined)
718
- return false
776
+ const childNodes = node.childNodes;
777
+ if (childNodes === null || childNodes === undefined) return false;
719
778
 
720
779
  for (let i = 0; i < childNodes.length; i++) {
721
- let child = childNodes[i]
780
+ let child = childNodes[i];
722
781
  // console.log("Checking child:"+child.nodeType+":"+child.tagName+":"+child.nodeValue)
723
- let found = findGlobalOffset(child, nodeToFind, offset, result)
724
- if (found)
725
- return true
782
+ let found = findGlobalOffset(child, nodeToFind, offset, result);
783
+ if (found) return true;
726
784
  }
727
785
  if (node.nodeType === 1 && node.tagName === "P") {
728
- result.offset ++
786
+ result.offset++;
729
787
  }
730
788
 
731
789
  return false;
732
790
  }
733
791
 
734
- function findNodeAndOffsetForGlobalOffset(node, globalOffset, result=null) {
792
+ function findNodeAndOffsetForGlobalOffset(node, globalOffset, result = null) {
735
793
  if (isKatexNode(node)) {
736
794
  result.node = null;
737
795
  result.offset++;
738
796
  return false;
739
797
  }
740
798
  if (isTextNode(node)) {
741
- result.node = node
742
- result.lastTextNode = node
799
+ result.node = node;
800
+ result.lastTextNode = node;
743
801
  // console.log("Found text node \'"+node.nodeValue+"\' of length "+node.nodeValue.length)
744
802
  // console.log("result.offset was: "+result.offset)
745
- result.offset += node.nodeValue.length
803
+ result.offset += node.nodeValue.length;
746
804
  if (nodeStartsWithSmallSpace(node)) {
747
805
  // console.log("Text node starts with small space")
748
- result.offset -= 1
806
+ result.offset -= 1;
749
807
  }
750
808
  // console.log("result.offset now: "+result.offset)
751
- return false
809
+ return false;
752
810
  }
753
- const childNodes = node.childNodes
754
- if (childNodes === null || childNodes === undefined)
755
- return false
811
+ const childNodes = node.childNodes;
812
+ if (childNodes === null || childNodes === undefined) return false;
756
813
 
757
814
  for (let i = 0; i < childNodes.length; i++) {
758
- let found = findNodeAndOffsetForGlobalOffset(childNodes[i], globalOffset, result)
759
- if (found)
760
- return true
815
+ let found = findNodeAndOffsetForGlobalOffset(
816
+ childNodes[i],
817
+ globalOffset,
818
+ result
819
+ );
820
+ if (found) return true;
761
821
 
762
822
  if (result.node !== null && result.node !== undefined) {
763
823
  if (result.offset >= globalOffset) {
764
- result.offset = globalOffset-(result.offset-result.node.nodeValue.length)
765
- return true
824
+ result.offset =
825
+ globalOffset - (result.offset - result.node.nodeValue.length);
826
+ return true;
766
827
  }
767
828
  }
768
829
  }
769
830
 
770
831
  if (node.nodeType === 1 && node.tagName === "P") {
771
832
  // console.log("Leaving p node")
772
- result.node = null
773
- result.offset ++
833
+ result.node = null;
834
+ result.offset++;
774
835
  // console.log("result.offset now: "+result.offset)
775
836
  }
776
837
  return false;
777
838
  }
778
839
 
779
840
  export function findFirstTextNode(node) {
780
- if (isTextNode(node))
781
- return node
841
+ if (isTextNode(node)) return node;
782
842
 
783
- const nodes = node.childNodes
843
+ const nodes = node.childNodes;
784
844
  for (let i = 0; i < nodes.length; i++) {
785
- let found_node = findFirstTextNode(nodes[i])
786
- if (found_node !== null)
787
- return found_node
845
+ let found_node = findFirstTextNode(nodes[i]);
846
+ if (found_node !== null) return found_node;
788
847
  }
789
848
 
790
- return null
849
+ return null;
791
850
  }
792
851
 
793
- function findNodeAndOffsetForGlobalOffsetInEditableDiv(editableDiv, globalOffset) {
794
- let result = {offset:0}
852
+ function findNodeAndOffsetForGlobalOffsetInEditableDiv(
853
+ editableDiv,
854
+ globalOffset
855
+ ) {
856
+ let result = { offset: 0 };
795
857
  // console.log("Finding node and offset within: "+editableDiv.outerHTML)
796
- findNodeAndOffsetForGlobalOffset(editableDiv, globalOffset, result)
858
+ findNodeAndOffsetForGlobalOffset(editableDiv, globalOffset, result);
797
859
  // console.log(result)
798
860
 
799
861
  if (result.node === null) {
800
862
  if (globalOffset > 0 && result.lastTextNode !== null) {
801
- result.node = result.lastTextNode
802
- result.offset = result.lastTextNode.nodeValue.length
863
+ result.node = result.lastTextNode;
864
+ result.offset = result.lastTextNode.nodeValue.length;
803
865
  } else {
804
- console.warn("Unable to find node and offset for global offset: "+globalOffset)
805
- console.log(editableDiv.innerHTML)
806
- console.log(result)
807
- result.node = findFirstTextNode(editableDiv)
808
- result.offset = 0
866
+ console.warn(
867
+ "Unable to find node and offset for global offset: " + globalOffset
868
+ );
869
+ console.log(editableDiv.innerHTML);
870
+ console.log(result);
871
+ result.node = findFirstTextNode(editableDiv);
872
+ result.offset = 0;
809
873
  }
810
874
  }
811
875
 
812
- if (result.offset === 0 &&
876
+ if (
877
+ result.offset === 0 &&
813
878
  result.node.nodeValue.length > 0 &&
814
- nodeStartsWithSmallSpace(result.node)) {
815
- result.offset = 1
879
+ nodeStartsWithSmallSpace(result.node)
880
+ ) {
881
+ result.offset = 1;
816
882
  }
817
883
 
818
884
  if (result.offset > result.node.nodeValue.length) {
819
- console.warn("Unexpected offset when converting global offset to node and offset")
820
- console.log("Global offset: "+globalOffset)
821
- console.log("Result:")
822
- console.log(result)
823
- console.log("result.offset > result.node.nodeValue.length")
824
- result.offset = result.node.nodeValue.length
885
+ console.warn(
886
+ "Unexpected offset when converting global offset to node and offset"
887
+ );
888
+ console.log("Global offset: " + globalOffset);
889
+ console.log("Result:");
890
+ console.log(result);
891
+ console.log("result.offset > result.node.nodeValue.length");
892
+ console.log("Node value: '" + result.node.nodeValue + "'");
893
+ console.log("Node value length: " + result.node.nodeValue.length);
894
+ console.log("Calculated offset: " + result.offset);
895
+ result.offset = result.node.nodeValue.length;
825
896
  }
826
897
 
827
- return result
898
+ return result;
828
899
  }
829
900
 
830
901
  // https://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
@@ -833,14 +904,18 @@ export function getRangeParams(editableDiv) {
833
904
  // console.log("Inner html: "+editableDiv.innerHTML)
834
905
  const isSupported = typeof window.getSelection !== "undefined";
835
906
  if (!isSupported) {
836
- console.error("Can't get range params, browser does not support window.getSelection")
837
- return null
907
+ console.error(
908
+ "Can't get range params, browser does not support window.getSelection"
909
+ );
910
+ return null;
838
911
  }
839
912
 
840
913
  const selection = window.getSelection();
841
914
  if (selection.rangeCount === 0) {
842
- console.error("Can't get range params, window.getSelection did not return any ranges")
843
- return null
915
+ console.error(
916
+ "Can't get range params, window.getSelection did not return any ranges"
917
+ );
918
+ return null;
844
919
  }
845
920
 
846
921
  const range = selection.getRangeAt(0);
@@ -849,31 +924,61 @@ export function getRangeParams(editableDiv) {
849
924
  // If the range is not within a text node, move it to a text node
850
925
  // This typically happens when the end of a range is at the start of a new line
851
926
  if (range.startContainer.nodeType !== 3) {
852
- let textNode = findFirstTextNode(range.startContainer)
927
+ let textNode = findFirstTextNode(range.startContainer);
853
928
  if (textNode !== null) {
854
- console.warn("Adjusting start pos in getRangeParams as container was of type: "+range.startContainer.nodeName)
855
- range.setStart(textNode,0)
929
+ console.warn(
930
+ "Adjusting start pos in getRangeParams as container was of type: " +
931
+ range.startContainer.nodeName
932
+ );
933
+ range.setStart(textNode, 0);
856
934
  } else {
857
- console.error("Unable to getRangeParams. seletion.startContainer is not a text node. "+
858
- "Node type: "+range.startContainer.nodeName)
859
- return null
935
+ // Handle empty content case - return default range params
936
+ // This is normal behavior after selecting all and deleting content
937
+ console.log(
938
+ "getRangeParams: Empty content detected, using default range params. " +
939
+ "Node type: " +
940
+ range.startContainer.nodeName
941
+ );
942
+ return {
943
+ startNodeIndex: 0,
944
+ startOffset: 0,
945
+ endNodeIndex: 0,
946
+ endOffset: 0,
947
+ startGlobalOffset: 0,
948
+ endGlobalOffset: 0,
949
+ };
860
950
  }
861
951
  }
862
952
  if (range.endContainer.nodeType !== 3) {
863
- let textNode = findFirstTextNode(range.endContainer)
953
+ let textNode = findFirstTextNode(range.endContainer);
864
954
  if (textNode !== null) {
865
- console.warn("Adjusting end pos in getRangeParams as container was of type: "+range.endContainer.nodeName)
866
- range.setEnd(textNode,0)
955
+ console.warn(
956
+ "Adjusting end pos in getRangeParams as container was of type: " +
957
+ range.endContainer.nodeName
958
+ );
959
+ range.setEnd(textNode, 0);
867
960
  } else {
868
- console.error("Unable to getRangeParams. seletion.endContainer is not a text node. "+
869
- "Node type: "+range.endContainer.nodeName)
870
- return null
961
+ // Handle empty content case - return default range params
962
+ // This is normal behavior after selecting all and deleting content
963
+ console.log(
964
+ "getRangeParams: Empty content detected, using default range params. " +
965
+ "Node type: " +
966
+ range.endContainer.nodeName
967
+ );
968
+ return {
969
+ startNodeIndex: 0,
970
+ startOffset: 0,
971
+ endNodeIndex: 0,
972
+ endOffset: 0,
973
+ startGlobalOffset: 0,
974
+ endGlobalOffset: 0,
975
+ };
871
976
  }
872
977
  }
873
978
  // Find the index of the start and end nodes
874
979
 
875
- let startNodeIndex = findIndexOfNode(editableDiv, range.startContainer)
876
- let endNodeIndex = findIndexOfNode(editableDiv, range.endContainer)
980
+ let startNodeIndex = findIndexOfNode(editableDiv, range.startContainer);
981
+ let endNodeIndex = findIndexOfNode(editableDiv, range.endContainer);
877
982
 
878
983
  // console.log(editableDiv)
879
984
  // console.log("Start node: "+range.startContainer)
@@ -882,11 +987,16 @@ export function getRangeParams(editableDiv) {
882
987
  // console.log("Start node offset: "+range.startOffset)
883
988
 
884
989
  // Find the global offset of the start and end nodes
885
- let startResult = {offset:0}
886
- findGlobalOffset(editableDiv, range.startContainer, range.startOffset, startResult)
990
+ let startResult = { offset: 0 };
991
+ findGlobalOffset(
992
+ editableDiv,
993
+ range.startContainer,
994
+ range.startOffset,
995
+ startResult
996
+ );
887
997
  // console.log("Global offset of start: "+startResult.offset)
888
- let endResult = {offset:0}
889
- findGlobalOffset(editableDiv, range.endContainer, range.endOffset, endResult)
998
+ let endResult = { offset: 0 };
999
+ findGlobalOffset(editableDiv, range.endContainer, range.endOffset, endResult);
890
1000
  // console.log("Global offset of end: "+endResult.offset)
891
1001
 
892
1002
  let rangeParams = {
@@ -895,8 +1005,8 @@ export function getRangeParams(editableDiv) {
895
1005
  endNodeIndex: endNodeIndex,
896
1006
  endOffset: range.endOffset,
897
1007
  startGlobalOffset: startResult.offset,
898
- endGlobalOffset: endResult.offset
899
- }
1008
+ endGlobalOffset: endResult.offset,
1009
+ };
900
1010
 
901
1011
  return rangeParams;
902
1012
  }
@@ -905,12 +1015,16 @@ function setRangeParamsFromGlobalOffsets(editableDiv, params) {
905
1015
  let new_range = null;
906
1016
  try {
907
1017
  if (params === null || params === undefined)
908
- throw new Error("Can't set range params: params === "+params)
909
- let startResult =
910
- findNodeAndOffsetForGlobalOffsetInEditableDiv(editableDiv, params.startGlobalOffset)
1018
+ throw new Error("Can't set range params: params === " + params);
1019
+ let startResult = findNodeAndOffsetForGlobalOffsetInEditableDiv(
1020
+ editableDiv,
1021
+ params.startGlobalOffset
1022
+ );
911
1023
 
912
- let endResult =
913
- findNodeAndOffsetForGlobalOffsetInEditableDiv(editableDiv, params.endGlobalOffset)
1024
+ let endResult = findNodeAndOffsetForGlobalOffsetInEditableDiv(
1025
+ editableDiv,
1026
+ params.endGlobalOffset
1027
+ );
914
1028
 
915
1029
  if (startResult.node !== null) {
916
1030
  // console.log("Setting range to start in node:")
@@ -919,21 +1033,21 @@ function setRangeParamsFromGlobalOffsets(editableDiv, params) {
919
1033
  // console.log(" node value: "+startResult.node.nodeValue)
920
1034
  // console.log(" offset:"+startResult.offset)
921
1035
  } else {
922
- console.warn("Can't set range, null start node returned")
923
- return
1036
+ console.warn("Can't set range, null start node returned");
1037
+ return;
924
1038
  }
925
1039
 
926
1040
  new_range = document.createRange();
927
1041
  new_range.setStart(startResult.node, startResult.offset);
928
1042
  new_range.setEnd(endResult.node, endResult.offset);
929
1043
  const selection = window.getSelection();
930
- selection.removeAllRanges()
931
- selection.addRange(new_range)
1044
+ selection.removeAllRanges();
1045
+ selection.addRange(new_range);
932
1046
  } catch (error) {
933
- console.error(error)
934
- console.log("Trying to set range:")
935
- console.log(new_range)
936
- console.log(editableDiv.childNodes)
1047
+ console.error(error);
1048
+ console.log("Trying to set range:");
1049
+ console.log(new_range);
1050
+ console.log(editableDiv.childNodes);
937
1051
  }
938
1052
  }
939
1053
 
@@ -944,54 +1058,67 @@ export function setRangeParams(editableDiv, params) {
944
1058
  let new_range = null;
945
1059
  try {
946
1060
  if (params === null || params === undefined) {
947
- console.error("Can't set range params: params === "+params)
948
- return
1061
+ console.error("Can't set range params: params === " + params);
1062
+ return;
949
1063
  }
950
1064
 
951
1065
  // New way of doing things
952
- if (params.startGlobalOffset !== null && params.startGlobalOffset !== undefined) {
1066
+ if (
1067
+ params.startGlobalOffset !== null &&
1068
+ params.startGlobalOffset !== undefined
1069
+ ) {
953
1070
  // console.log("Setting range from global offsets")
954
- setRangeParamsFromGlobalOffsets(editableDiv, params)
955
- return
1071
+ setRangeParamsFromGlobalOffsets(editableDiv, params);
1072
+ return;
956
1073
  }
957
1074
 
958
1075
  // Otherwise use the old way of doing things
959
1076
  // console.log("Setting range from node index and local offset")
960
1077
 
961
1078
  if (params.startNodeIndex === null || params.startNodeIndex === undefined) {
962
- console.error("Can't set range params: params.startNodeIndex === "+params.startNodeIndex)
963
- return
1079
+ console.error(
1080
+ "Can't set range params: params.startNodeIndex === " +
1081
+ params.startNodeIndex
1082
+ );
1083
+ return;
964
1084
  }
965
1085
  if (params.startNodeIndex < 0) {
966
- console.error("Can't set range params: params.startNodeIndex < 0")
967
- return
1086
+ console.error("Can't set range params: params.startNodeIndex < 0");
1087
+ return;
968
1088
  }
969
1089
 
970
- let nodes = editableDiv.childNodes
971
- if (params.startNodeIndex === 0 && params.startOffset === 0 && nodes.length === 0) {
972
- return
1090
+ let nodes = editableDiv.childNodes;
1091
+ if (
1092
+ params.startNodeIndex === 0 &&
1093
+ params.startOffset === 0 &&
1094
+ nodes.length === 0
1095
+ ) {
1096
+ return;
973
1097
  }
974
1098
 
975
1099
  // Set the offsets within the nodes
976
- let startOffset = params.startOffset
977
- let endOffset = params.endOffset
1100
+ let startOffset = params.startOffset;
1101
+ let endOffset = params.endOffset;
978
1102
 
979
1103
  // Handle special case of auto inserted SMALL CHAR at beginning of input
980
1104
  // TODO This sectin may no longer be neceesary and may be introducing a bug
981
- if (params.startNodeIndex === 0 &&
1105
+ if (
1106
+ params.startNodeIndex === 0 &&
982
1107
  params.startOffset === 1 &&
983
- isSmallSpace(nodes[0]))
984
- startOffset = 0
1108
+ isSmallSpace(nodes[0])
1109
+ )
1110
+ startOffset = 0;
985
1111
 
986
- if (params.endNodeIndex === 0 &&
1112
+ if (
1113
+ params.endNodeIndex === 0 &&
987
1114
  params.endOffset === 1 &&
988
- isSmallSpace(nodes[0]))
989
- endOffset = 0
990
-
1115
+ isSmallSpace(nodes[0])
1116
+ )
1117
+ endOffset = 0;
991
1118
 
992
1119
  // Find the nodes
993
- let startNode = findNodeWithIndex(editableDiv, params.startNodeIndex)
994
- let endNode = findNodeWithIndex(editableDiv, params.endNodeIndex)
1120
+ let startNode = findNodeWithIndex(editableDiv, params.startNodeIndex);
1121
+ let endNode = findNodeWithIndex(editableDiv, params.endNodeIndex);
995
1122
 
996
1123
  // console.log("Setting range to start in node:")
997
1124
  // console.log(" node type: "+startNode.nodeType)
@@ -1007,16 +1134,18 @@ export function setRangeParams(editableDiv, params) {
1007
1134
 
1008
1135
  // Set range in document
1009
1136
  if (startNode === null) {
1010
- console.warn("Could not set range to start node with index "+params.startNodeIndex)
1011
- startNode = findFirstTextNode(editableDiv)
1012
- startOffset = 0
1137
+ console.warn(
1138
+ "Could not set range to start node with index " + params.startNodeIndex
1139
+ );
1140
+ startNode = findFirstTextNode(editableDiv);
1141
+ startOffset = 0;
1013
1142
  }
1014
1143
 
1015
1144
  if (!isTextNode(startNode)) {
1016
- console.warn("Start node found for range start is not a text a node")
1017
- console.log(editableDiv.childNodes)
1018
- console.log(params)
1019
- console.log(startNode)
1145
+ console.warn("Start node found for range start is not a text a node");
1146
+ console.log(editableDiv.childNodes);
1147
+ console.log(params);
1148
+ console.log(startNode);
1020
1149
  // startNode = findFirstTextNode(startNode)
1021
1150
  // if (startNode === null)
1022
1151
  // startNode = findFirstTextNode(editableDiv)
@@ -1024,16 +1153,18 @@ export function setRangeParams(editableDiv, params) {
1024
1153
  }
1025
1154
 
1026
1155
  if (endNode === null) {
1027
- console.warn("Could not set range to end node with index "+params.endNodeIndex)
1028
- endNode = findFirstTextNode(editableDiv)
1029
- endOffset = 0
1156
+ console.warn(
1157
+ "Could not set range to end node with index " + params.endNodeIndex
1158
+ );
1159
+ endNode = findFirstTextNode(editableDiv);
1160
+ endOffset = 0;
1030
1161
  }
1031
1162
 
1032
1163
  if (!isTextNode(endNode)) {
1033
- console.warn("End node found for range start is not a text a node")
1034
- console.log(editableDiv.childNodes)
1035
- console.log(params)
1036
- console.log(startNode)
1164
+ console.warn("End node found for range start is not a text a node");
1165
+ console.log(editableDiv.childNodes);
1166
+ console.log(params);
1167
+ console.log(startNode);
1037
1168
  // startNode = findFirstTextNode(startNode)
1038
1169
  // if (startNode === null)
1039
1170
  // startNode = findFirstTextNode(editableDiv)
@@ -1042,73 +1173,89 @@ export function setRangeParams(editableDiv, params) {
1042
1173
 
1043
1174
  new_range = document.createRange();
1044
1175
  if (startOffset > startNode.nodeValue.length) {
1045
- console.warn("endOffset > endNode.nodeValue.length when setting range: "+
1046
- startOffset+ " > "+startNode.nodeValue.length+
1047
- " - "+stateNode.nodeType+
1048
- " - "+startNode.nodeValue)
1049
- startOffset = startNode.nodeValue.length
1176
+ console.warn(
1177
+ "endOffset > endNode.nodeValue.length when setting range: " +
1178
+ startOffset +
1179
+ " > " +
1180
+ startNode.nodeValue.length +
1181
+ " - " +
1182
+ stateNode.nodeType +
1183
+ " - " +
1184
+ startNode.nodeValue
1185
+ );
1186
+ startOffset = startNode.nodeValue.length;
1050
1187
  }
1051
1188
 
1052
1189
  new_range.setStart(startNode, startOffset);
1053
1190
  if (endOffset > endNode.nodeValue.length) {
1054
- console.warn("endOffset > endNode.nodeValue.length when setting range: "+
1055
- endOffset+ " > "+endNode.nodeValue.length+
1056
- " - "+endNode.nodeType+
1057
- " - "+endNode.nodeValue)
1058
- endOffset = endNode.nodeValue.length
1191
+ console.warn(
1192
+ "endOffset > endNode.nodeValue.length when setting range: " +
1193
+ endOffset +
1194
+ " > " +
1195
+ endNode.nodeValue.length +
1196
+ " - " +
1197
+ endNode.nodeType +
1198
+ " - " +
1199
+ endNode.nodeValue
1200
+ );
1201
+ endOffset = endNode.nodeValue.length;
1059
1202
  }
1060
1203
  new_range.setEnd(endNode, endOffset);
1061
1204
 
1062
1205
  // console.log("Setting range")
1063
1206
  // console.log(new_range)
1064
1207
  const selection = window.getSelection();
1065
- selection.removeAllRanges()
1066
- selection.addRange(new_range)
1208
+ selection.removeAllRanges();
1209
+ selection.addRange(new_range);
1067
1210
  } catch (error) {
1068
- console.error(error)
1069
- console.log("Trying to set range:")
1070
- console.log(new_range)
1071
- console.log(editableDiv.childNodes)
1211
+ console.error(error);
1212
+ console.log("Trying to set range:");
1213
+ console.log(new_range);
1214
+ console.log(editableDiv.childNodes);
1072
1215
  }
1073
1216
  }
1074
1217
 
1075
-
1076
-
1077
1218
  // https://stackoverflow.com/questions/6659351/removing-all-script-tags-from-html-with-js-regular-expression
1078
1219
  export function stripScripts(s) {
1079
1220
  do {
1080
- var div = document.createElement('div');
1221
+ var div = document.createElement("div");
1081
1222
  div.innerHTML = s;
1082
- var scripts = div.getElementsByTagName('script');
1223
+ var scripts = div.getElementsByTagName("script");
1083
1224
  var i = scripts.length;
1084
1225
  while (i--) {
1085
1226
  scripts[i].parentNode.removeChild(scripts[i]);
1086
1227
  }
1087
1228
  s = div.innerHTML;
1088
- } while (scripts !== null && scripts !== undefined && scripts.length > 0)
1229
+ } while (scripts !== null && scripts !== undefined && scripts.length > 0);
1089
1230
 
1090
- return s
1231
+ return s;
1091
1232
  }
1092
1233
 
1093
1234
  function sanitiseHtmlForElement(element) {
1094
- if (isTextNode(element))
1095
- return document.createTextNode(element.nodeValue)
1235
+ if (isTextNode(element)) return document.createTextNode(element.nodeValue);
1096
1236
  if (isKatexNode(element)) {
1097
1237
  // console.log("Copying katex node")
1098
- let clone = element.cloneNode(true)
1099
- return clone
1238
+ let clone = element.cloneNode(true);
1239
+ return clone;
1100
1240
  }
1101
- if (element.tagName === "HEAD" ||
1241
+ // Preserve math tags - they should be processed as math content
1242
+ if (element.nodeType === 1 && element.tagName.toLowerCase() === "math") {
1243
+ let clone = element.cloneNode(true);
1244
+ return clone;
1245
+ }
1246
+ if (
1247
+ element.tagName === "HEAD" ||
1102
1248
  element.tagName === "STYLE" ||
1103
- element.tagName === "LINK")
1104
- return null
1105
- let new_element = null
1249
+ element.tagName === "LINK"
1250
+ )
1251
+ return null;
1252
+ let new_element = null;
1106
1253
  if (isBIUNode(element)) {
1107
1254
  // console.log("Updating descendent: "+element.textContent)
1108
1255
  new_element = document.createElement(element.tagName);
1109
- } else if (element.nodeType === 1 &&
1110
- (
1111
- element.tagName === "LI" ||
1256
+ } else if (
1257
+ element.nodeType === 1 &&
1258
+ (element.tagName === "LI" ||
1112
1259
  element.tagName === "DT" ||
1113
1260
  element.tagName === "DD" ||
1114
1261
  element.tagName === "THEAD" ||
@@ -1118,126 +1265,209 @@ function sanitiseHtmlForElement(element) {
1118
1265
  element.tagName === "H3" ||
1119
1266
  element.tagName === "H4" ||
1120
1267
  element.tagName === "H5" ||
1121
- element.tagName === "H6"
1122
- )) {
1268
+ element.tagName === "H6")
1269
+ ) {
1123
1270
  // console.log("Converting descendent: <"+element.tagName+">"+element.textContent)
1124
- new_element = document.createElement('p');
1271
+ new_element = document.createElement("p");
1125
1272
  } else {
1126
1273
  // console.log("Stripping descendent: <"+element.tagName+">"+element.textContent)
1127
- new_element = document.createElement('span');
1274
+ new_element = document.createElement("span");
1128
1275
  }
1129
1276
  if (element.hasChildNodes()) {
1130
1277
  for (let i = 0; i < element.childNodes.length; i++) {
1131
- let new_child = sanitiseHtmlForElement(element.childNodes[i])
1132
- if (new_child !== null)
1133
- new_element.appendChild(new_child)
1134
- if (element.childNodes[i].tagName === "TH" || element.childNodes[i].tagName === "TD")
1135
- new_element.appendChild(document.createTextNode(" "))
1278
+ let new_child = sanitiseHtmlForElement(element.childNodes[i]);
1279
+ if (new_child !== null) new_element.appendChild(new_child);
1280
+ if (
1281
+ element.childNodes[i].tagName === "TH" ||
1282
+ element.childNodes[i].tagName === "TD"
1283
+ )
1284
+ new_element.appendChild(document.createTextNode(" "));
1136
1285
  }
1137
1286
  }
1138
- return new_element
1287
+ return new_element;
1139
1288
  }
1140
- const SPAN_TAG = new RegExp("<span>|</span>","g")
1289
+ const SPAN_TAG = new RegExp("<span>|</span>", "g");
1141
1290
  // const BR_TAG_REG_EXP = new RegExp("<br[\s\S]*?>","g")
1142
1291
  // const COMMENT_TAG_REG_EXP = new RegExp("<!--.+?-->","g")
1143
1292
  // const COMMENT_TAG_REG_EXP = new RegExp("(?=<!--([\s\S]*?)-->","g")
1144
1293
  // const COMMENT_TAG_REG_EXP = new RegExp("<!--[\s\S]*?-->","gm")
1145
1294
  // const MULTI_WHITE_SPACE_REG_EXP = new RegExp("\s{2,}","gm")
1146
- const KATEX_START_HTML = "<span class=\"katex\""
1147
- const KATEX_END_HTML = "</span></span></span></span>"
1295
+ const KATEX_START_HTML = '<span class="katex"';
1296
+ const KATEX_END_HTML = "</span></span></span></span>";
1148
1297
  export function sanitiseHtml(s) {
1149
1298
  // Remove all <br> tags (as some of these can be inside katex tags)
1150
- s = s.replace(/<br[\s\S]*?>/gm,"")
1299
+ s = s.replace(/<br[\s\S]*?>/gm, "");
1151
1300
  // console.log("After removing comments:")
1152
1301
  // console.log(s)
1153
1302
  // Next, convert all elements to supported elements
1154
- var div = document.createElement('div');
1303
+ var div = document.createElement("div");
1155
1304
  div.innerHTML = s;
1156
- let sanitisedElement = sanitiseHtmlForElement(div)
1157
- let sanitised = sanitisedElement.innerHTML
1305
+ let sanitisedElement = sanitiseHtmlForElement(div);
1306
+ let sanitised = sanitisedElement.innerHTML;
1158
1307
  // Next, remove all span tags, except those for katex span nodes
1159
1308
  // NB: A simpler solution may have been to create something other than
1160
1309
  // span tags in the sanitiseHtmlForElement(div) method and then simply
1161
1310
  // remove all span tags.
1162
- let i = 0
1163
- let j = 0
1164
- let its = 0
1165
- let output = []
1311
+ let i = 0;
1312
+ let j = 0;
1313
+ let its = 0;
1314
+ let output = [];
1166
1315
  do {
1167
- its++
1168
- j = sanitised.indexOf(KATEX_START_HTML,i)
1316
+ its++;
1317
+ j = sanitised.indexOf(KATEX_START_HTML, i);
1169
1318
  if (j < 0) {
1170
- let part = sanitised.substring(i,sanitised.length)
1171
- part = part.replace(SPAN_TAG,"")
1172
- output.push(part)
1173
- break
1319
+ let part = sanitised.substring(i, sanitised.length);
1320
+ part = part.replace(SPAN_TAG, "");
1321
+ output.push(part);
1322
+ break;
1174
1323
  }
1175
- let part = sanitised.substring(i,j)
1176
- part = part.replace(SPAN_TAG,"")
1177
- output.push(part)
1178
- let k = sanitised.indexOf(KATEX_END_HTML, j) + KATEX_END_HTML.length
1324
+ let part = sanitised.substring(i, j);
1325
+ part = part.replace(SPAN_TAG, "");
1326
+ output.push(part);
1327
+ let k = sanitised.indexOf(KATEX_END_HTML, j) + KATEX_END_HTML.length;
1179
1328
  if (k < 0) {
1180
- console.warn("Unable to find closing katex tag")
1181
- k = sanitised.length
1329
+ console.warn("Unable to find closing katex tag");
1330
+ k = sanitised.length;
1182
1331
  }
1183
- output.push(sanitised.substring(j,k))
1184
- i = k
1332
+ output.push(sanitised.substring(j, k));
1333
+ i = k;
1334
+ } while (j > 0 && its < 20);
1185
1335
 
1186
- } while (j > 0 && its < 20)
1187
-
1188
- sanitised = output.join("")
1336
+ sanitised = output.join("");
1189
1337
  // Remove new lines
1190
- sanitised = sanitised.replace(/(\r\n|\r|\n)/gm, "") // new line
1338
+ sanitised = sanitised.replace(/(\r\n|\r|\n)/gm, ""); // new line
1191
1339
  // Convert any multispaces to a single space
1192
- sanitised = sanitised.replace(/\s{2,}/gm, " ") // 2 or more white spaces
1340
+ sanitised = sanitised.replace(/\s{2,}/gm, " "); // 2 or more white spaces
1193
1341
  // Next, make sure that each line of html is wrapped in paragraph tags
1194
- i = 0
1195
- j = 0
1196
- its = 0
1197
- output = []
1342
+ i = 0;
1343
+ j = 0;
1344
+ its = 0;
1345
+ output = [];
1198
1346
  do {
1199
- its++
1200
- j = sanitised.indexOf("</p>",i)
1347
+ its++;
1348
+ j = sanitised.indexOf("</p>", i);
1201
1349
  if (j < 0) {
1202
- output.push(sanitised.substring(i,sanitised.length))
1203
- break
1350
+ output.push(sanitised.substring(i, sanitised.length));
1351
+ break;
1204
1352
  }
1205
- j = j+4 // Move j four characters to end of </p> tag
1206
- output.push(sanitised.substring(i,j)) // copy as far as </p>
1353
+ j = j + 4; // Move j four characters to end of </p> tag
1354
+ output.push(sanitised.substring(i, j)); // copy as far as </p>
1207
1355
  // if we have reached the end of the string, finish
1208
- if (j === sanitised.length)
1209
- break
1356
+ if (j === sanitised.length) break;
1210
1357
  // if not enough characters for a <p> then can't be a <p>
1211
- if (j+3 > sanitised.length) {
1212
- output.push("<p>")
1213
- output.push(sanitised.substring(j,sanitised.length))
1214
- output.push("</p>")
1215
- break
1358
+ if (j + 3 > sanitised.length) {
1359
+ output.push("<p>");
1360
+ output.push(sanitised.substring(j, sanitised.length));
1361
+ output.push("</p>");
1362
+ break;
1216
1363
  }
1217
1364
  // If the next characters are the start of a paragraph tag then continue
1218
- let nextChars = sanitised.substring(j,j+3)
1365
+ let nextChars = sanitised.substring(j, j + 3);
1219
1366
  if (nextChars === "<p>") {
1220
- i = j
1221
- continue
1367
+ i = j;
1368
+ continue;
1222
1369
  }
1223
1370
  // If the next characters are the start of a katex tag then continue
1224
- if (j+KATEX_START_HTML.length < sanitised.length) {
1225
- nextChars = sanitised.substring(j,j+KATEX_START_HTML.length)
1371
+ if (j + KATEX_START_HTML.length < sanitised.length) {
1372
+ nextChars = sanitised.substring(j, j + KATEX_START_HTML.length);
1226
1373
  if (nextChars === KATEX_START_HTML) {
1227
- i = j
1228
- continue
1374
+ i = j;
1375
+ continue;
1229
1376
  }
1230
1377
  }
1231
1378
  // Ok! We have found some html that is not wrapped in a paragraph tag
1232
1379
  // Lets insert the paragraph tags
1233
- output.push("<p>")
1234
- let k = sanitised.indexOf("<p>",j)
1235
- if (k < 0)
1236
- k = sanitised.length
1237
- output.push(sanitised.substring(j,k))
1238
- output.push("</p>")
1239
- i = k
1240
- } while (j > 0 && its < 20)
1241
- sanitised = output.join("")
1242
- return sanitised
1243
- }
1380
+ output.push("<p>");
1381
+ let k = sanitised.indexOf("<p>", j);
1382
+ if (k < 0) k = sanitised.length;
1383
+ output.push(sanitised.substring(j, k));
1384
+ output.push("</p>");
1385
+ i = k;
1386
+ } while (j > 0 && its < 20);
1387
+ sanitised = output.join("");
1388
+ return sanitised;
1389
+ }
1390
+
1391
+ // Helper functions for advanced text navigation
1392
+ export function isWordBoundary(char) {
1393
+ // Word boundaries are spaces, punctuation, or start/end of string
1394
+ return !char || /[\s\.,;:!?\-\(\)\[\]{}'"]/g.test(char);
1395
+ }
1396
+
1397
+ export function findWordBoundary(text, startPos, direction) {
1398
+ // direction: 1 for forward, -1 for backward
1399
+ let pos = startPos;
1400
+ let len = text.length;
1401
+
1402
+ if (direction === 1) {
1403
+ // Moving forward - find end of current word, then skip whitespace
1404
+ while (pos < len && !isWordBoundary(text[pos])) {
1405
+ pos++;
1406
+ }
1407
+ while (pos < len && isWordBoundary(text[pos])) {
1408
+ pos++;
1409
+ }
1410
+ } else {
1411
+ // Moving backward - find start of current word, then skip whitespace
1412
+ if (pos > 0) pos--; // Move back one character first
1413
+ while (pos > 0 && isWordBoundary(text[pos])) {
1414
+ pos--;
1415
+ }
1416
+ while (pos > 0 && !isWordBoundary(text[pos - 1])) {
1417
+ pos--;
1418
+ }
1419
+ }
1420
+
1421
+ return Math.max(0, Math.min(pos, len));
1422
+ }
1423
+
1424
+ export function findLineBoundary(text, startPos, direction) {
1425
+ // direction: 1 for end of line, -1 for start of line
1426
+ let pos = startPos;
1427
+ let len = text.length;
1428
+
1429
+ if (direction === 1) {
1430
+ // Find end of current line
1431
+ while (pos < len && text[pos] !== "\n") {
1432
+ pos++;
1433
+ }
1434
+ } else {
1435
+ // Find start of current line
1436
+ while (pos > 0 && text[pos - 1] !== "\n") {
1437
+ pos--;
1438
+ }
1439
+ }
1440
+
1441
+ return Math.max(0, Math.min(pos, len));
1442
+ }
1443
+
1444
+ export function getPlainTextFromEditableDiv(editableDiv) {
1445
+ // Convert the editable div content to plain text for boundary detection
1446
+ let text = elementToMarkedRawText(editableDiv, null, 0, false);
1447
+ text = removeMarks(text);
1448
+ // Remove HTML tags and convert to plain text
1449
+ text = text.replace(/<[^>]*>/g, "");
1450
+ text = text.replace(/&nbsp;/g, " ");
1451
+ text = text.replace(/&amp;/g, "&");
1452
+ text = text.replace(/&lt;/g, "<");
1453
+ text = text.replace(/&gt;/g, ">");
1454
+ text = text.replace(/&quot;/g, '"');
1455
+ text = text.replace(/&#39;/g, "'");
1456
+ return text;
1457
+ }
1458
+
1459
+ export function globalOffsetToPlainTextOffset(editableDiv, globalOffset) {
1460
+ // Convert global offset to plain text offset for boundary detection
1461
+ let plainText = getPlainTextFromEditableDiv(editableDiv);
1462
+ // This is a simplified mapping - in a real implementation you'd want
1463
+ // to account for the difference between HTML and plain text offsets
1464
+ return Math.min(globalOffset, plainText.length);
1465
+ }
1466
+
1467
+ export function plainTextOffsetToGlobalOffset(editableDiv, plainTextOffset) {
1468
+ // Convert plain text offset back to global offset
1469
+ // This is a simplified mapping - in a real implementation you'd want
1470
+ // to account for the difference between HTML and plain text offsets
1471
+ let plainText = getPlainTextFromEditableDiv(editableDiv);
1472
+ return Math.min(plainTextOffset, plainText.length);
1473
+ }