@zzish/math-rich-input 0.1.47 → 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.
- package/dist/index.js +1040 -117
- package/dist/math-rich-input.css +1 -1
- package/dist/standalone.js +2 -0
- package/dist/standalone.js.LICENSE.txt +29 -0
- package/package.json +13 -3
- package/src/EquationEditor.jsx +557 -527
- package/src/EquationEditorModal.jsx +104 -58
- package/src/MathRichInput.jsx +2892 -1924
- package/src/Toolbar.jsx +234 -219
- package/src/mathRichInputHelper.js +787 -557
- package/src/standalone.js +110 -0
- package/webpack.standalone.config.js +63 -0
- package/dist/index.css +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { determineMimeType } from
|
|
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 =
|
|
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 =
|
|
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
|
|
40
|
-
const MIME_TYPE_HTML
|
|
41
|
-
const MIME_TYPE_TEX
|
|
42
|
-
const MIME_TYPE_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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 ===
|
|
77
|
-
|
|
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 (
|
|
81
|
+
if (
|
|
82
|
+
node.nodeType === 3 &&
|
|
83
83
|
node.nodeValue.length >= 1 &&
|
|
84
|
-
|
|
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(
|
|
91
|
+
if (
|
|
92
|
+
node.nodeType === 3 &&
|
|
91
93
|
node.nodeValue.length === 1 &&
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
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
|
-
|
|
118
|
-
else if (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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,"&")
|
|
243
|
-
text = text.replace(/>/g,">")
|
|
244
|
-
text = text.replace(/</g,"<")
|
|
245
|
-
text = text.replace(/'/g,"'")
|
|
246
|
-
text = text.replace(/"/g,""")
|
|
247
|
-
text = text.replace(/\$/g,"$")
|
|
248
|
-
text = text.replace(/,/g,",")
|
|
249
|
-
text = text.replace(/:/g,":")
|
|
250
|
-
text = text.replace(/\|/g,"|")
|
|
251
|
-
return text
|
|
254
|
+
text = text.replace(/&/g, "&");
|
|
255
|
+
text = text.replace(/>/g, ">");
|
|
256
|
+
text = text.replace(/</g, "<");
|
|
257
|
+
text = text.replace(/'/g, "'");
|
|
258
|
+
text = text.replace(/"/g, """);
|
|
259
|
+
text = text.replace(/\$/g, "$");
|
|
260
|
+
text = text.replace(/,/g, ",");
|
|
261
|
+
text = text.replace(/:/g, ":");
|
|
262
|
+
text = text.replace(/\|/g, "|");
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
270
|
+
"#36": "$",
|
|
271
|
+
quot: '"',
|
|
272
|
+
apos: "'",
|
|
273
|
+
lt: "<",
|
|
274
|
+
gt: ">",
|
|
275
|
+
amp: "&",
|
|
276
|
+
"#44": ",",
|
|
277
|
+
"#58": ":",
|
|
278
|
+
"#124": "|",
|
|
267
279
|
},
|
|
268
|
-
|
|
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
|
-
|
|
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(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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(
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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(
|
|
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
|
-
|
|
394
|
-
|
|
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 (
|
|
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 =
|
|
421
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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(
|
|
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(
|
|
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] === "")
|
|
542
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 (
|
|
601
|
-
|
|
602
|
-
|
|
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 (
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
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(
|
|
759
|
-
|
|
760
|
-
|
|
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 =
|
|
765
|
-
|
|
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(
|
|
794
|
-
|
|
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(
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
result
|
|
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 (
|
|
876
|
+
if (
|
|
877
|
+
result.offset === 0 &&
|
|
813
878
|
result.node.nodeValue.length > 0 &&
|
|
814
|
-
nodeStartsWithSmallSpace(result.node)
|
|
815
|
-
|
|
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(
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
console.log(
|
|
823
|
-
console.log("
|
|
824
|
-
|
|
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(
|
|
837
|
-
|
|
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(
|
|
843
|
-
|
|
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(
|
|
855
|
-
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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(
|
|
866
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
963
|
-
|
|
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 (
|
|
972
|
-
|
|
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 (
|
|
1105
|
+
if (
|
|
1106
|
+
params.startNodeIndex === 0 &&
|
|
982
1107
|
params.startOffset === 1 &&
|
|
983
|
-
|
|
984
|
-
|
|
1108
|
+
isSmallSpace(nodes[0])
|
|
1109
|
+
)
|
|
1110
|
+
startOffset = 0;
|
|
985
1111
|
|
|
986
|
-
if (
|
|
1112
|
+
if (
|
|
1113
|
+
params.endNodeIndex === 0 &&
|
|
987
1114
|
params.endOffset === 1 &&
|
|
988
|
-
|
|
989
|
-
|
|
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(
|
|
1011
|
-
|
|
1012
|
-
|
|
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(
|
|
1028
|
-
|
|
1029
|
-
|
|
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(
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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(
|
|
1221
|
+
var div = document.createElement("div");
|
|
1081
1222
|
div.innerHTML = s;
|
|
1082
|
-
var scripts = div.getElementsByTagName(
|
|
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
|
-
|
|
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
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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 (
|
|
1110
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
1122
|
-
|
|
1268
|
+
element.tagName === "H6")
|
|
1269
|
+
) {
|
|
1123
1270
|
// console.log("Converting descendent: <"+element.tagName+">"+element.textContent)
|
|
1124
|
-
new_element = document.createElement(
|
|
1271
|
+
new_element = document.createElement("p");
|
|
1125
1272
|
} else {
|
|
1126
1273
|
// console.log("Stripping descendent: <"+element.tagName+">"+element.textContent)
|
|
1127
|
-
new_element = document.createElement(
|
|
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
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1237
|
-
output.push(
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
sanitised
|
|
1242
|
-
|
|
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(/ /g, " ");
|
|
1451
|
+
text = text.replace(/&/g, "&");
|
|
1452
|
+
text = text.replace(/</g, "<");
|
|
1453
|
+
text = text.replace(/>/g, ">");
|
|
1454
|
+
text = text.replace(/"/g, '"');
|
|
1455
|
+
text = text.replace(/'/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
|
+
}
|