@zzish/math-rich-input 0.1.0

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.
@@ -0,0 +1,785 @@
1
+ import { determineMimeType } from './mimeTypeHelper'
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>"
7
+
8
+ const INLINE_TEX_REG_EXP = /\$\$[\s\S]+?\$\$|\\\[[\s\S]+?\\\]|\\\([\s\S]+?\\\)|\$[^$\\]*(?:\\.[^$\\]*)*\$/g;
9
+ const BLOCK_TEX_REG_EXP = /\$\$[\s\S]+?\$\$|\\\[[\s\S]+?\\\]/g;
10
+
11
+ // const HTML_MATH_REG_EXP = /<math>.*?<\/math>/gi;
12
+ const HTML_MATH_REG_EXP = /<math>[\s\S]*?<\/math>/gi;
13
+
14
+ const SPAN_OPEN_BASE = "<span class=\"base\">"
15
+ const SPAN_CLOSE = "</span>"
16
+
17
+ const MARK = "_M_A_R_K_"
18
+ const MARK_REG_EXP = new RegExp(MARK, "g")
19
+
20
+ const SMALL_SPACE_CHAR_CODE = 8203
21
+ const SMALL_SPACE = String.fromCharCode(SMALL_SPACE_CHAR_CODE)
22
+ const SMALL_SPACE_REG_EXP = new RegExp(SMALL_SPACE, "g")
23
+
24
+ const P_TAG_REG_EXP = new RegExp("<[p|P]>", "g")
25
+ const P_ELEMENT_SMALL_SPACE = "<p>" + SMALL_SPACE
26
+
27
+ const MIME_TYPE_PLAIN_TEXT = "text/plain"
28
+ const MIME_TYPE_HTML = "text/html"
29
+ const MIME_TYPE_TEX = "application/x-tex"
30
+ const MIME_TYPE_LATEX = "application/x-latex"
31
+ const MIME_TYPE_ZZISH_HTML_MATH = "application/x-zzish-html-math"
32
+
33
+ export function isKatexNode(node) {
34
+ return node.className === "katex" || node.className === "katex-error"
35
+ }
36
+
37
+ function isTextNode(node) {
38
+ return node.nodeType === 3
39
+ }
40
+
41
+ function isBIUNode(node) {
42
+ if (node.nodeType !== 1)
43
+ return false
44
+
45
+ const tagName = node.tagName.toLowerCase()
46
+ return false ||
47
+ tagName === "b" ||
48
+ tagName === "i" ||
49
+ tagName === "u" ||
50
+ tagName === "sup" ||
51
+ tagName === "sub" ||
52
+ tagName === "p"
53
+ }
54
+
55
+ function isBrNode(node) {
56
+ if (node.tagName === undefined)
57
+ return false
58
+ return node.tagName.toLowerCase() === "br"
59
+ }
60
+
61
+ function isSmallSpace(node) {
62
+ if(node.nodeType === 3 &&
63
+ node.nodeValue.length === 1 &&
64
+ node.nodeValue.charCodeAt(0) === SMALL_SPACE_CHAR_CODE)
65
+ return true
66
+
67
+ return false
68
+ }
69
+
70
+ export function startsWithSmallSpace (node) {
71
+ let textNode = findFirstTextNode(node)
72
+ return isSmallSpace(textNode)
73
+
74
+ }
75
+
76
+ export function countNodeTypes(node, counter=null) {
77
+ if (counter === null)
78
+ counter = {
79
+ math:0,
80
+ html:0,
81
+ text:0
82
+ }
83
+
84
+ if (node == null || node === undefined) {
85
+ console.error("Node: "+node)
86
+ return counter
87
+ }
88
+
89
+ if (isKatexNode(node))
90
+ counter.math++
91
+ else if (isBIUNode(node))
92
+ counter.html++
93
+ else if (isTextNode(node))
94
+ counter.text++
95
+
96
+ if (isKatexNode(node))
97
+ return counter
98
+
99
+ if (node.childNodes === null || node.childNodes === undefined)
100
+ return counter
101
+
102
+ for (let i = 0; i < node.childNodes.length; i++)
103
+ countNodeTypes(node.childNodes[i], counter)
104
+
105
+ return counter
106
+ }
107
+
108
+ export function getNodeText(node) {
109
+ // console.log("getNodeText")
110
+ // console.log(node)
111
+
112
+ if (node.nodeType === 3)
113
+ return node.nodeValue
114
+ else if (node.nodeType === 1) {
115
+ node = findFirstTextNode(node)
116
+ return node.nodeValue
117
+ } else {
118
+ console.error("Trying to get length of node that is not text or BUI: ")
119
+ console.log(node)
120
+ return null
121
+ }
122
+ }
123
+
124
+ export function getNodeTextLength(node) {
125
+ let text = getNodeText(node)
126
+ if (text === null || text === undefined)
127
+ return -1
128
+
129
+ return text.length
130
+ }
131
+
132
+ export function removeMarks(raw_text) {
133
+ return raw_text.replace(MARK_REG_EXP,"")
134
+ }
135
+
136
+ export function getCharacterBeforeMarks(text) {
137
+ let start = text.indexOf(MARK)
138
+ if (start < 1) {
139
+ console.warn("Can't get character before mark. Start: "+start)
140
+ return null
141
+ }
142
+ return text.charAt(start-1)
143
+ }
144
+
145
+ export function replaceCharacterBeforeMarks(text, character) {
146
+ let start = text.indexOf(MARK)
147
+ if (start < 1) {
148
+ console.warn("Can't replace character before mark. Start: "+start)
149
+ return text
150
+ }
151
+ return text.substring(0, start-1) + character + text.substring(start)
152
+ }
153
+
154
+ export function replaceStringBeforeMarks(text, oldString, newString) {
155
+ let start = text.indexOf(MARK)
156
+ if (start < 1) {
157
+ console.warn("Can't replace character before mark. Start: "+start)
158
+ return text
159
+ }
160
+ return text.substring(0, start-oldString.length) + newString + text.substring(start)
161
+ }
162
+
163
+
164
+ export function insertCharacterBeforeMarks(text, character) {
165
+ let start = text.indexOf(MARK)
166
+ if (start < 0) {
167
+ console.warn("Can't replace character before mark. Start: "+start)
168
+ return text
169
+ }
170
+ return text.substring(0, start) + character + text.substring(start)
171
+ }
172
+
173
+ // export function replaceCharacterAfterMarks(text, character) {
174
+ // let start = text.indexOf(MARK) + MARK.length
175
+ // let end = text.indexOf(MARK, start) + MARK.length
176
+ // return text.substring(0, end) + character + text.substring(end+1)
177
+ // }
178
+
179
+ function getFirstDescendentByTagName(node, tagName) {
180
+ if (node.tagName === tagName)
181
+ return node
182
+
183
+ if (node.childNodes !== null && node.childNodes !== undefined)
184
+ for (let i = 0; i < node.childNodes.length; i++) {
185
+ let result = getFirstDescendentByTagName(node.childNodes[i],tagName)
186
+ if (result !== null)
187
+ return result
188
+ }
189
+ return null
190
+ }
191
+
192
+ export function getLatex(node) {
193
+ try {
194
+ // Example:
195
+ // <annotation encoding="application/x-tex">L' = {L}{\sqrt{1-\frac{v^2}{c^2}}}</annotation>
196
+ let annotation = getFirstDescendentByTagName(node, "annotation")
197
+ let latex = annotation.childNodes[0].nodeValue
198
+ latex = decodeLatex(latex)
199
+ return latex
200
+ } catch (error) {
201
+ console.error(error)
202
+ console.log(node)
203
+ return "error"
204
+ }
205
+ }
206
+
207
+
208
+ function encodeToHtml(text) {
209
+ text = text.replace(/&/g,"&amp;")
210
+ text = text.replace(/>/g,"&gt;")
211
+ text = text.replace(/</g,"&lt;")
212
+ text = text.replace(/'/g,"&apos;")
213
+ text = text.replace(/"/g,"&quot;")
214
+ text = text.replace(/\$/g,"&#36;")
215
+ return text
216
+ }
217
+
218
+ var decodeFromHtml = (function() {
219
+ var translate_re = /&(#36|quot|apos|lt|gt|amp);/g,
220
+ translate = {
221
+ // 'nbsp': String.fromCharCode(160),
222
+ '#36': '$',
223
+ 'quot': '"',
224
+ 'apos': '\'',
225
+ 'lt' : '<',
226
+ 'gt' : '>',
227
+ 'amp' : '&'
228
+ },
229
+ translator = function($0, $1) {
230
+ return translate[$1];
231
+ };
232
+
233
+ return function(s) {
234
+ // return s
235
+ return s.replace(translate_re, translator);
236
+ };
237
+ })();
238
+
239
+ // function prettifyLatex(latex) {
240
+ // latex = latex.replace(/\\/g," \\")
241
+ // latex = latex.replace("="," = ")
242
+ // latex = latex.replace("{ ","{")
243
+ // latex = latex.replace(" "," ")
244
+ // latex.trim()
245
+ // console.log("Prettified: "+latex)
246
+ // return latex
247
+ // }
248
+
249
+ // Note we don't need to encode it so do nothing here
250
+ function encodeLatex(text) {
251
+ // console.log("Pre-encoded: "+text)
252
+ // console.log("Post-encoded: "+text)
253
+ return text
254
+ }
255
+
256
+ // Note we don't need to decode it so do nothing here
257
+ function decodeLatex(text) {
258
+ // console.log("Pre-decoded: "+text)
259
+ // console.log("Post-decoded: "+text)
260
+ return text
261
+ }
262
+
263
+ function addText(arrayOfText, text, mark, offset) {
264
+ // console.log("Mark: "+mark)
265
+ if (offset === 0) {
266
+ if (mark) {
267
+ arrayOfText.push(MARK)
268
+ // arrayOfText.push(" ")
269
+ arrayOfText.push(MARK)
270
+ }
271
+ arrayOfText.push(text)
272
+ } else {
273
+ let decoded = decodeFromHtml(text)
274
+ // console.log("Decoded: "+decoded)
275
+ arrayOfText.push(encodeToHtml(decoded.substring(0,offset)))
276
+ if (mark) {
277
+ arrayOfText.push(MARK)
278
+ // arrayOfText.push(" ")
279
+ arrayOfText.push(MARK)
280
+ }
281
+ arrayOfText.push(encodeToHtml(decoded.substring(offset)))
282
+ }
283
+ }
284
+
285
+ function addTextForNode(node, arrayOfText, nodeToMark=null, offset=0, useHtmlMath=true) {
286
+ let mark = false
287
+ if (node === nodeToMark)
288
+ mark = true
289
+ // console.log("Node: "+node+" "+mark)
290
+
291
+ try {
292
+ if (isTextNode(node)) {
293
+ addText(arrayOfText,
294
+ useHtmlMath ? encodeToHtml(node.nodeValue) : node.nodeValue,
295
+ mark, offset, useHtmlMath)
296
+ } else if (isBrNode(node)) {
297
+ useHtmlMath && arrayOfText.push("<br>")
298
+ } else if (isBIUNode(node)) {
299
+ useHtmlMath && arrayOfText.push("<"+node.nodeName+">")
300
+ for (let i = 0; i < node.childNodes.length; i++) {
301
+ addTextForNode(node.childNodes[i], arrayOfText, nodeToMark, offset, useHtmlMath)
302
+ }
303
+ useHtmlMath && arrayOfText.push("</"+node.nodeName+">")
304
+ } else if (isKatexNode(node)) {
305
+ if (mark)
306
+ arrayOfText.push(MARK)
307
+ arrayOfText.push(useHtmlMath ? RAW_TEXT_MATH_START_TAG_HTML_MATH : RAW_TEXT_MATH_START_TAG_TEX)
308
+ arrayOfText.push(encodeLatex(getLatex(node)))
309
+ arrayOfText.push(useHtmlMath ? RAW_TEXT_MATH_END_TAG_HTML_MATH : RAW_TEXT_MATH_END_TAG_TEX)
310
+ if (mark)
311
+ arrayOfText.push(MARK)
312
+ } else {
313
+ console.error("Cant render node in editable div: Unknown node type")
314
+ console.log(node)
315
+ console.log("Node.type:"+node.type)
316
+ console.log("Node.tagName:"+node.tagName)
317
+ }
318
+ } catch (error) {
319
+ console.error(error)
320
+ console.log("Node: ")
321
+ console.log(node)
322
+ console.log("Node.type:"+node.type)
323
+ console.log("Node.tagName:"+node.tagName)
324
+ }
325
+ }
326
+
327
+ // This method is similar to the innerText property of a specified node except that
328
+ // it automatically converts complex katex maths nodes back to simple latex enclosed
329
+ // in the specified custom tags and also adds marks to a specific node if a nodeToMark
330
+ // is specified.
331
+ //
332
+ // Marked nodes are useful if a node may need to be deleted and allows the node to be
333
+ // deleted by stripping it out of the raw text.
334
+ export function elementToMarkedRawText(element, nodeToMark=null, offset=0, useHtmlMath=true) {
335
+ // console.log("Node to mark:")
336
+ // console.log(nodeToMark)
337
+ let nodes = element.childNodes
338
+ // console.log(nodes)
339
+
340
+ let result = []
341
+
342
+ if ((nodes === null || nodes === undefined || nodes.length === 0) && nodeToMark !== null) {
343
+ // console.log("Empty field")
344
+ addText(result, "", true, 0)
345
+ } else {
346
+ for (let i = 0; i < nodes.length; i++)
347
+ addTextForNode(nodes[i], result, nodeToMark, offset, useHtmlMath)
348
+ }
349
+ let raw_text = result.join("")
350
+
351
+ // remove any zero-width spaces we added to the html
352
+ raw_text = raw_text.replace(SMALL_SPACE_REG_EXP,'')
353
+
354
+ return raw_text
355
+ }
356
+
357
+ export function replaceMarksWithMath(markedRawText, latex, useHtmlMath=true) {
358
+ let start_pos = markedRawText.indexOf(MARK)
359
+ let end_pos = markedRawText.indexOf(MARK, start_pos + MARK.length)
360
+ let new_raw_text = markedRawText.substring(0,start_pos) +
361
+ (useHtmlMath ? RAW_TEXT_MATH_START_TAG_HTML_MATH : RAW_TEXT_MATH_START_TAG_TEX) +
362
+ latex +
363
+ (useHtmlMath ? RAW_TEXT_MATH_END_TAG_HTML_MATH : RAW_TEXT_MATH_END_TAG_TEX) +
364
+ markedRawText.substring(end_pos + MARK.length)
365
+ return new_raw_text
366
+ }
367
+
368
+ function stripDollars (stringToStrip) {
369
+ return stringToStrip[0] === "$" && stringToStrip[1] !== "$"
370
+ ? stringToStrip.slice(1, -1)
371
+ : stringToStrip.slice(2, -2)
372
+ }
373
+
374
+ function stripTags (stringToStrip) {
375
+ let i = stringToStrip.indexOf(">")
376
+ let j = stringToStrip.lastIndexOf("<")
377
+ if (i <= 0 || j <= 0) {
378
+ console.error("Could not strip tag from string: "+stringToStrip)
379
+ return stringToStrip
380
+ }
381
+
382
+ return stringToStrip.substring(i+1,j)
383
+ }
384
+
385
+ function getDisplayType (stringToDisplay) {
386
+ return stringToDisplay.match(BLOCK_TEX_REG_EXP) ? "block" : "inline"
387
+ }
388
+
389
+ function renderLatexString (katex, s, t) {
390
+ let options = {
391
+ children: "",
392
+ displayMode: false,
393
+ output: "htmlAndMathml",
394
+ leqno: false,
395
+ fleqn: false,
396
+ throwOnError: false,
397
+ errorColor: "#cc0000",
398
+ macros: {},
399
+ minRuleThickness: 0,
400
+ colorIsTextColor: false,
401
+ strict: "warn",
402
+ trust: false
403
+ }
404
+
405
+ let renderedString;
406
+ try {
407
+ // returns HTML markup
408
+ renderedString = katex.renderToString(
409
+ s,
410
+ t === "block" ? Object.assign({ displayMode: true }, options) : options
411
+ );
412
+ } catch (err) {
413
+ console.error("couldn`t convert string", s);
414
+ return s;
415
+ }
416
+
417
+ // Reduce multiple base class spans to one single span
418
+ try {
419
+ let fixed_renderedString = renderedString
420
+ let i = fixed_renderedString.indexOf(SPAN_OPEN_BASE)
421
+ do {
422
+ i = fixed_renderedString.indexOf(SPAN_OPEN_BASE, i + SPAN_OPEN_BASE.length)
423
+ if (i < 0)
424
+ break;
425
+ let j = fixed_renderedString.lastIndexOf(SPAN_CLOSE, i)
426
+ fixed_renderedString =
427
+ fixed_renderedString.substring(0,j) +
428
+ fixed_renderedString.substring(i+SPAN_OPEN_BASE.length)
429
+ } while (i > 0)
430
+ renderedString = fixed_renderedString
431
+ } catch (error) {
432
+ console.log(error)
433
+ }
434
+
435
+ return renderedString;
436
+ };
437
+
438
+ export function rawTextToHtml(katex, string, mimeType) {
439
+
440
+ if (string === "")
441
+ return ""
442
+
443
+ if (mimeType === null || mimeType === undefined || mimeType === "")
444
+ mimeType = determineMimeType(string)
445
+
446
+ if (mimeType === MIME_TYPE_PLAIN_TEXT)
447
+ return string
448
+
449
+ let parseAsTex = false
450
+ if (mimeType === MIME_TYPE_TEX || mimeType === MIME_TYPE_LATEX)
451
+ parseAsTex = true
452
+ if (!parseAsTex && (mimeType !== MIME_TYPE_HTML && mimeType !== MIME_TYPE_ZZISH_HTML_MATH)) {
453
+ console.error("Unknown mime type: "+mimeType)
454
+ }
455
+
456
+ const stringElements = string.split(parseAsTex ? INLINE_TEX_REG_EXP : HTML_MATH_REG_EXP);
457
+
458
+ // Insert small space at start of first element if it is an empty element
459
+ if (stringElements[0] === "") // This means the original string stars with latex
460
+ stringElements[0] = "&#" + SMALL_SPACE.charCodeAt(0)
461
+
462
+ // Insert small space at end of <p></p> elements so that empty lines get rendered
463
+ if (mimeType === MIME_TYPE_ZZISH_HTML_MATH || mimeType === MIME_TYPE_HTML) {
464
+ for (let i = 0; i <stringElements.length; i++)
465
+ stringElements[i] = stringElements[i].replace(P_TAG_REG_EXP, P_ELEMENT_SMALL_SPACE)
466
+ }
467
+
468
+ if (stringElements.length === 1)
469
+ return stringElements[0]
470
+
471
+ const latexElements = string.match(parseAsTex ? INLINE_TEX_REG_EXP : HTML_MATH_REG_EXP);
472
+
473
+ const result = [];
474
+ for (let i = 0; i < stringElements.length; i++) {
475
+ result.push(stringElements[i])
476
+
477
+ if (latexElements[i]) {
478
+ let element = latexElements[i]
479
+ const type = parseAsTex ? getDisplayType(element) : "inline"
480
+ element = parseAsTex ? stripDollars(element) : stripTags(element)
481
+
482
+ let renderedElement = renderLatexString(katex, element, type)
483
+ renderedElement = renderedElement + "&#"+SMALL_SPACE.charCodeAt(0)
484
+
485
+ result.push(renderedElement)
486
+ }
487
+ }
488
+
489
+ return result.join("")
490
+ }
491
+
492
+ export function isEmptyNode(node) {
493
+ return node.childNodes === null || node.childNodes === undefined || node.childNodes.length === 0
494
+ }
495
+
496
+ export function findIndexOfNode(node, nodeToFind, indexCounter=null) {
497
+ if (indexCounter === null) {
498
+ // Starting with root node
499
+ if (node === nodeToFind)
500
+ return 0
501
+ indexCounter = {count:0}
502
+ } else {
503
+ if (isKatexNode(node)) {
504
+ if (node === nodeToFind || node.contains(nodeToFind))
505
+ return indexCounter.count
506
+ else {
507
+ indexCounter.count ++
508
+ return -1
509
+ }
510
+ }
511
+
512
+ if (isBrNode(node)) {
513
+ if (node === nodeToFind)
514
+ return indexCounter.count
515
+ else {
516
+ const nextSibling = node.nextSibling
517
+ if (nextSibling !== null && nextSibling !== undefined && isBrNode(nextSibling)) {
518
+ indexCounter.count ++
519
+ return -1
520
+ } else {
521
+ return -1
522
+ }
523
+
524
+ }
525
+ }
526
+
527
+ if (isTextNode(node)) {
528
+ if (node === nodeToFind)
529
+ return indexCounter.count
530
+
531
+ indexCounter.count ++
532
+ }
533
+
534
+ }
535
+
536
+ const childNodes = node.childNodes
537
+ if (childNodes === null || childNodes === undefined)
538
+ return -1
539
+
540
+ for (let i = 0; i < childNodes.length; i++) {
541
+ let index = findIndexOfNode(childNodes[i], nodeToFind, indexCounter)
542
+ if (index >= 0)
543
+ return index
544
+ }
545
+
546
+ return -1
547
+ }
548
+
549
+ export function findNodeWithIndex(node, index, indexCounter=null) {
550
+ if (indexCounter === null) {
551
+ // Starting with root node
552
+ if (index === 0 && isEmptyNode(node)) {
553
+ // console.log("Empty node")
554
+ return node
555
+ }
556
+ indexCounter = {count:0}
557
+ } else {
558
+ if (isKatexNode(node)) {
559
+ if (index === indexCounter.count)
560
+ return node
561
+ else {
562
+ indexCounter.count ++
563
+ return null
564
+ }
565
+ }
566
+
567
+ if (isBrNode(node)) {
568
+ if (index === indexCounter.count)
569
+ return node
570
+ else {
571
+ const nextSibling = node.nextSibling
572
+ if (nextSibling !== null && nextSibling !== undefined && isBrNode(nextSibling)) {
573
+ indexCounter.count ++
574
+ return null
575
+ } else {
576
+ return null
577
+ }
578
+
579
+ }
580
+ }
581
+
582
+ // If not the root node then
583
+ if (isTextNode(node)) {
584
+ if (index === indexCounter.count)
585
+ return node
586
+
587
+ indexCounter.count ++
588
+ }
589
+ }
590
+
591
+ const childNodes = node.childNodes
592
+ if (childNodes === null || childNodes === undefined)
593
+ return null
594
+
595
+ for (let i = 0; i < childNodes.length; i++) {
596
+ let found_node = findNodeWithIndex(childNodes[i], index, indexCounter)
597
+ if (found_node !== null) {
598
+ return found_node
599
+ }
600
+ }
601
+
602
+ return null
603
+ }
604
+
605
+ export function findFirstTextNode(node) {
606
+ if (isTextNode(node))
607
+ return node
608
+
609
+ const nodes = node.childNodes
610
+ for (let i = 0; i < nodes.length; i++) {
611
+ let found_node = findFirstTextNode(nodes[i])
612
+ if (found_node !== null)
613
+ return found_node
614
+ }
615
+
616
+ return null
617
+ }
618
+
619
+ export function getRangeParams(editableDiv) {
620
+ // console.log ("getRangeParams")
621
+ // console.log("Inner html: "+editableDiv.innerHTML)
622
+ const isSupported = typeof window.getSelection !== "undefined";
623
+ if (!isSupported) {
624
+ console.error("Can't get range params, browser does not support window.getSelection")
625
+ return null
626
+ }
627
+
628
+ const selection = window.getSelection();
629
+ if (selection.rangeCount === 0) {
630
+ console.error("Can't get range params, window.getSelection did not return any ranges")
631
+ return null
632
+ }
633
+
634
+ const range = selection.getRangeAt(0);
635
+
636
+ let startNodeIndex = findIndexOfNode(editableDiv, range.startContainer)
637
+ let endNodeIndex = findIndexOfNode(editableDiv, range.endContainer)
638
+
639
+ // console.log(editableDiv)
640
+ // console.log("Start node: "+range.startContainer)
641
+ // console.log("Start node: |"+range.startContainer.nodeValue+"|")
642
+ // console.log("Start node index found: "+startNodeIndex)
643
+ // console.log("Start node offset: "+range.startOffset)
644
+
645
+ let rangeParams = {
646
+ startNodeIndex: startNodeIndex,
647
+ startOffset: range.startOffset,
648
+ endNodeIndex: endNodeIndex,
649
+ endOffset: range.endOffset
650
+ }
651
+
652
+ return rangeParams
653
+ }
654
+
655
+ export function setRangeParams(editableDiv, params) {
656
+ // console.log("setRangeParams")
657
+ // console.log(params)
658
+
659
+ let new_range = null;
660
+ try {
661
+ if (params === null || params === undefined) {
662
+ console.error("Can't set range params: params === "+params)
663
+ return
664
+ }
665
+ if (params.startNodeIndex === null || params.startNodeIndex === undefined) {
666
+ console.error("Can't set range params: params.startNodeIndex === "+params.startNodeIndex)
667
+ return
668
+ }
669
+
670
+ // console.log("Setting cursor to range params: "+params.startNodeIndex+"->"+params.startOffset)
671
+ // console.log(params)
672
+
673
+ if (params.startNodeIndex < 0) {
674
+ console.error("Can't set range params: params.startNodeIndex < 0")
675
+ return
676
+ }
677
+
678
+
679
+ let nodes = editableDiv.childNodes
680
+ if (params.startNodeIndex === 0 && params.startOffset === 0 && nodes.length === 0) {
681
+ // console.log("Not setting range params")
682
+ return
683
+ }
684
+
685
+ // console.log("Node["+params.startNodeIndex+"]:")
686
+ // console.log(nodes[params.startNodeIndex])
687
+ let startOffset = params.startOffset
688
+ let endOffset = params.endOffset
689
+
690
+ // Handle special case of auto inserted SMALL CHAR at beginning of input
691
+ if (params.startNodeIndex === 0 &&
692
+ params.startOffset === 1 &&
693
+ isSmallSpace(nodes[0]))
694
+ startOffset = 0
695
+
696
+ if (params.endNodeIndex === 0 &&
697
+ params.endOffset === 1 &&
698
+ isSmallSpace(nodes[0]))
699
+ endOffset = 0
700
+
701
+ let startNode = findNodeWithIndex(editableDiv, params.startNodeIndex)
702
+ let endNode = findNodeWithIndex(editableDiv, params.endNodeIndex)
703
+
704
+ // Lets make sure we are setting
705
+ // if (params.startNodeIndex !== 0 &&
706
+ // params.startOffset === 0) {
707
+ // if (getNodeText(startNode).charAt(0) === SMALL_SPACE)
708
+ // startOffset = SMALL_SPACE.length
709
+ // }
710
+ // if (params.endNodeIndex !== 0 &&
711
+ // params.endOffset === 0) {
712
+ // if (getNodeText(endNode).charAt(0) === SMALL_SPACE)
713
+ // endOffset = SMALL_SPACE.length
714
+ // }
715
+ // console.log("End: "+params.endNodeIndex+"->"+endOffset)
716
+
717
+ // Set range in document
718
+ if (startNode === null) {
719
+ console.error("Could not set range to start node with index "+params.startNodeIndex)
720
+ startNode = findFirstTextNode(editableDiv)
721
+ startOffset = 0
722
+ }
723
+
724
+ if (!isTextNode(startNode)) {
725
+ console.warn("Start node found for range start is not a text a node")
726
+ console.log(editableDiv.childNodes)
727
+ console.log(params)
728
+ console.log(startNode)
729
+ // startNode = findFirstTextNode(startNode)
730
+ // if (startNode === null)
731
+ // startNode = findFirstTextNode(editableDiv)
732
+ // startOffset = 0
733
+ }
734
+
735
+ if (endNode === null) {
736
+ console.error("Could not set range to start node with index "+params.endNodeIndex)
737
+ endNode = findFirstTextNode(editableDiv)
738
+ endOffset = 0
739
+ }
740
+
741
+ if (!isTextNode(endNode)) {
742
+ console.warn("End node found for range start is not a text a node")
743
+ console.log(editableDiv.childNodes)
744
+ console.log(params)
745
+ console.log(startNode)
746
+ // startNode = findFirstTextNode(startNode)
747
+ // if (startNode === null)
748
+ // startNode = findFirstTextNode(editableDiv)
749
+ // startOffset = 0
750
+ }
751
+
752
+ new_range = document.createRange();
753
+ new_range.setStart(startNode, startOffset);
754
+ new_range.setEnd(endNode, endOffset);
755
+
756
+ // console.log("Setting range")
757
+ // console.log(new_range)
758
+ const selection = window.getSelection();
759
+ selection.removeAllRanges()
760
+ selection.addRange(new_range)
761
+ } catch (error) {
762
+ console.error(error)
763
+ console.log("Trying to set range:")
764
+ console.log(new_range)
765
+ console.log(editableDiv.childNodes)
766
+ }
767
+ }
768
+
769
+
770
+
771
+ // https://stackoverflow.com/questions/6659351/removing-all-script-tags-from-html-with-js-regular-expression
772
+ export function stripScripts(s) {
773
+ do {
774
+ var div = document.createElement('div');
775
+ div.innerHTML = s;
776
+ var scripts = div.getElementsByTagName('script');
777
+ var i = scripts.length;
778
+ while (i--) {
779
+ scripts[i].parentNode.removeChild(scripts[i]);
780
+ }
781
+ s = div.innerHTML;
782
+ } while (scripts !== null && scripts !== undefined && scripts.length > 0)
783
+
784
+ return s
785
+ }