@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.
- package/README.md +253 -0
- package/babel.config.js +6 -0
- package/dist/index.js +23034 -0
- package/dist/math-rich-input.css +923 -0
- package/package.json +44 -0
- package/rollup.config.js +31 -0
- package/src/AccentBar.css +169 -0
- package/src/AccentBar.jsx +771 -0
- package/src/EquationEditor.css +258 -0
- package/src/EquationEditor.jsx +583 -0
- package/src/EquationEditorModal.css +164 -0
- package/src/EquationEditorModal.jsx +81 -0
- package/src/MathRichArea.jsx +103 -0
- package/src/MathRichInput.css +63 -0
- package/src/MathRichInput.jsx +1651 -0
- package/src/SymbolButton.css +130 -0
- package/src/SymbolButton.jsx +96 -0
- package/src/Toolbar.css +142 -0
- package/src/Toolbar.jsx +219 -0
- package/src/equationEditorButtonsHelper.js +590 -0
- package/src/index.js +5 -0
- package/src/katexHelper.js +39 -0
- package/src/mathRichInputHelper.js +785 -0
- package/src/mimeTypeHelper.js +201 -0
|
@@ -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,"&")
|
|
210
|
+
text = text.replace(/>/g,">")
|
|
211
|
+
text = text.replace(/</g,"<")
|
|
212
|
+
text = text.replace(/'/g,"'")
|
|
213
|
+
text = text.replace(/"/g,""")
|
|
214
|
+
text = text.replace(/\$/g,"$")
|
|
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
|
+
}
|