@nectary/components 2.6.0 → 2.7.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/button/index.js +23 -9
- package/button/types.d.ts +14 -6
- package/button/utils.js +1 -1
- package/emoji/index.js +3 -3
- package/emoji/utils.d.ts +3 -1
- package/emoji/utils.js +17 -12
- package/emoji-picker/data.json +1 -1
- package/emoji-picker/index.js +7 -3
- package/icon-button/index.js +4 -2
- package/input/index.js +3 -2
- package/link/index.js +1 -1
- package/package.json +2 -3
- package/pop/index.js +1 -1
- package/popover/index.js +7 -1
- package/rich-text/index.d.ts +3 -0
- package/rich-text/index.js +35 -8
- package/rich-text/utils.d.ts +5 -0
- package/rich-text/utils.js +96 -1
- package/rich-textarea/index.d.ts +11 -0
- package/rich-textarea/index.js +464 -0
- package/rich-textarea/types.d.ts +48 -0
- package/rich-textarea/types.js +1 -0
- package/rich-textarea/utils.d.ts +39 -0
- package/rich-textarea/utils.js +1730 -0
- package/select-button/index.js +5 -3
- package/textarea/index.js +73 -16
- package/tooltip/index.js +7 -1
- package/utils/markdown.d.ts +19 -1
- package/utils/markdown.js +128 -113
|
@@ -0,0 +1,1730 @@
|
|
|
1
|
+
import { getEmojiBaseUrl, getEmojiUrl } from '../emoji/utils';
|
|
2
|
+
import { isEmojiString, parseMarkdown } from '../utils';
|
|
3
|
+
const TEXT_EMPTY_DATA = '';
|
|
4
|
+
const TEXT_WHITESPACE = ' ';
|
|
5
|
+
const isTextNode = $n => $n !== null && $n.nodeType === Node.TEXT_NODE;
|
|
6
|
+
const isEmptyText = value => value === null || value.length === 0 || value === TEXT_EMPTY_DATA;
|
|
7
|
+
const isEmptyTextNode = $n => isTextNode($n) && isEmptyText($n.nodeValue);
|
|
8
|
+
const getTextValue = $n => isEmptyText($n.nodeValue) ? TEXT_EMPTY_DATA : $n.nodeValue;
|
|
9
|
+
const isParagraph = $n => {
|
|
10
|
+
return $n !== null && $n.nodeType === Node.ELEMENT_NODE && $n.classList.contains('p');
|
|
11
|
+
};
|
|
12
|
+
const isListItem = $n => {
|
|
13
|
+
return isUnorderedListItem($n) || isOrderedListItem($n);
|
|
14
|
+
};
|
|
15
|
+
const isUnorderedListItem = $n => {
|
|
16
|
+
return $n !== null && $n.nodeType === Node.ELEMENT_NODE && $n.classList.contains('uli');
|
|
17
|
+
};
|
|
18
|
+
const isOrderedListItem = $n => {
|
|
19
|
+
return $n !== null && $n.nodeType === Node.ELEMENT_NODE && $n.classList.contains('oli');
|
|
20
|
+
};
|
|
21
|
+
const isTextBlock = $n => isParagraph($n) || isListItem($n);
|
|
22
|
+
const isEmoji = $n => {
|
|
23
|
+
return $n !== null && $n.nodeName === 'IMG';
|
|
24
|
+
};
|
|
25
|
+
const isInline = $n => $n !== null && $n.nodeName === 'SPAN';
|
|
26
|
+
const isRoot = $n => $n !== null && $n.nodeName === 'DIV';
|
|
27
|
+
const assertNonNull = $n => {
|
|
28
|
+
if ($n === null) {
|
|
29
|
+
throw new Error('Node is NULL');
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const assertEquals = (a, b) => {
|
|
33
|
+
if (a !== b) {
|
|
34
|
+
throw new Error(`"${a}" not equals "${b}"`);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const assertTextNode = $n => {
|
|
38
|
+
assertNonNull($n);
|
|
39
|
+
if (!isTextNode($n)) {
|
|
40
|
+
throw new Error(`Node is not a TextNode: ${$n?.nodeName}`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const assertTextBlock = $n => {
|
|
44
|
+
assertNonNull($n);
|
|
45
|
+
if (!isTextBlock($n)) {
|
|
46
|
+
throw new Error(`Node is not a TextBlock: ${$n?.nodeName}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const assertListItem = $n => {
|
|
50
|
+
assertNonNull($n);
|
|
51
|
+
if (!isListItem($n)) {
|
|
52
|
+
throw new Error(`Node is not a ListItem: ${$n?.nodeName}`);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const markListItemAsBlock = $li => {
|
|
56
|
+
$li.classList.add('block');
|
|
57
|
+
};
|
|
58
|
+
const isListItemMarkedArBlock = $li => {
|
|
59
|
+
return $li.classList.contains('block');
|
|
60
|
+
};
|
|
61
|
+
const MAX_LISTITEM_LEVEL = 4;
|
|
62
|
+
const removeListItemLevel = $li => {
|
|
63
|
+
$li.classList.remove('l0', 'l1', 'l2', 'l3', 'l4');
|
|
64
|
+
};
|
|
65
|
+
const setListItemLevel = ($li, level) => {
|
|
66
|
+
removeListItemLevel($li);
|
|
67
|
+
const clampedLevel = Math.max(0, Math.min(level, MAX_LISTITEM_LEVEL));
|
|
68
|
+
$li.classList.add(`l${clampedLevel}`);
|
|
69
|
+
};
|
|
70
|
+
const getListItemLevel = $li => {
|
|
71
|
+
if ($li.classList.contains('l1')) {
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
if ($li.classList.contains('l2')) {
|
|
75
|
+
return 2;
|
|
76
|
+
}
|
|
77
|
+
if ($li.classList.contains('l3')) {
|
|
78
|
+
return 3;
|
|
79
|
+
}
|
|
80
|
+
if ($li.classList.contains('l4')) {
|
|
81
|
+
return 4;
|
|
82
|
+
}
|
|
83
|
+
return 0;
|
|
84
|
+
};
|
|
85
|
+
const assertInline = $n => {
|
|
86
|
+
assertNonNull($n);
|
|
87
|
+
if (!isInline($n)) {
|
|
88
|
+
throw new Error(`Node is not an Inline: ${$n?.nodeName}`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const isTextContent = $n => {
|
|
92
|
+
return $n !== null && $n.nodeType === Node.ELEMENT_NODE && (isInline($n) || isEmoji($n));
|
|
93
|
+
};
|
|
94
|
+
const assertTextContent = $n => {
|
|
95
|
+
assertNonNull($n);
|
|
96
|
+
if (!isTextContent($n)) {
|
|
97
|
+
throw new Error(`Node is not TextContent: ${$n?.nodeName}`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const createTextNode = (data, doc) => {
|
|
101
|
+
return doc.createTextNode(data);
|
|
102
|
+
};
|
|
103
|
+
const createEmptyTextNode = doc => {
|
|
104
|
+
return createTextNode(TEXT_EMPTY_DATA, doc);
|
|
105
|
+
};
|
|
106
|
+
const createEmptyInline = doc => {
|
|
107
|
+
const res = doc.createElement('SPAN');
|
|
108
|
+
res.append(createEmptyTextNode(doc));
|
|
109
|
+
return res;
|
|
110
|
+
};
|
|
111
|
+
const getParentInline = $n => {
|
|
112
|
+
const $p = $n.parentNode;
|
|
113
|
+
assertInline($p);
|
|
114
|
+
return $p;
|
|
115
|
+
};
|
|
116
|
+
const isInsideList = (isOrdered, $n) => {
|
|
117
|
+
const $block = getParentTextBlock($n);
|
|
118
|
+
return isOrdered ? isOrderedListItem($block) : isUnorderedListItem($block);
|
|
119
|
+
};
|
|
120
|
+
const isAllInsideList = (isOrdered, $a, $b) => {
|
|
121
|
+
return allSiblingsIncludingSatisfy(getParentTextBlock($a), getParentTextBlock($b), isOrdered ? isOrderedListItem : isUnorderedListItem);
|
|
122
|
+
};
|
|
123
|
+
const FORMAT_TYPE_TO_NAME = {
|
|
124
|
+
formatBold: 'b',
|
|
125
|
+
formatCodeTag: 'c',
|
|
126
|
+
formatItalic: 'i',
|
|
127
|
+
formatStrikeThrough: 's'
|
|
128
|
+
};
|
|
129
|
+
const isFormatName = ($n, inlineName) => {
|
|
130
|
+
return $n.classList.contains(inlineName);
|
|
131
|
+
};
|
|
132
|
+
const isFormatBold = $n => isFormatName($n, 'b');
|
|
133
|
+
const isFormatItalic = $n => isFormatName($n, 'i');
|
|
134
|
+
const isFormatStrikethrough = $n => isFormatName($n, 's');
|
|
135
|
+
const isFormatCodetag = $n => isFormatName($n, 'c');
|
|
136
|
+
const isFormatLink = $n => isFormatName($n, 'l');
|
|
137
|
+
const isAllInsideFormatName = ($a, $b, formatName) => {
|
|
138
|
+
const aBlock = getParentTextBlock($a);
|
|
139
|
+
const bBlock = getParentTextBlock($b);
|
|
140
|
+
let $currentBlock = aBlock;
|
|
141
|
+
let $prevBlock = null;
|
|
142
|
+
let hasTextNodes = false;
|
|
143
|
+
do {
|
|
144
|
+
let $n = $currentBlock === getParentTextBlock($a) ? $a : getFirstTextContent($currentBlock);
|
|
145
|
+
let $prev = $n;
|
|
146
|
+
do {
|
|
147
|
+
if (isInline($n)) {
|
|
148
|
+
hasTextNodes = true;
|
|
149
|
+
if (!isFormatName($n, formatName)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
$prev = $n;
|
|
154
|
+
$n = getNextSiblingTextContent($n);
|
|
155
|
+
} while ($n !== null && $prev !== $b);
|
|
156
|
+
$prevBlock = $currentBlock;
|
|
157
|
+
$currentBlock = getNextSiblingTextBlock($currentBlock);
|
|
158
|
+
} while ($currentBlock !== null && $prevBlock !== bBlock);
|
|
159
|
+
return hasTextNodes;
|
|
160
|
+
};
|
|
161
|
+
const isAllInsideBold = ($a, $b) => isAllInsideFormatName($a, $b, 'b');
|
|
162
|
+
const isAllInsideItalic = ($a, $b) => isAllInsideFormatName($a, $b, 'i');
|
|
163
|
+
const isAllInsideStrikethrough = ($a, $b) => isAllInsideFormatName($a, $b, 's');
|
|
164
|
+
const isAllInsideCodetag = ($a, $b) => isAllInsideFormatName($a, $b, 'c');
|
|
165
|
+
const isAllInsideLink = ($a, $b) => isAllInsideFormatName($a, $b, 'l');
|
|
166
|
+
const LINK_HREF_ATTR_NAME = 'data-href';
|
|
167
|
+
const copyFormatName = ($source, $target) => {
|
|
168
|
+
const $inline = isTextNode($source) ? getParentInline($source) : $source;
|
|
169
|
+
$target.className = $inline.className;
|
|
170
|
+
if (isFormatLink($inline)) {
|
|
171
|
+
$target.setAttribute(LINK_HREF_ATTR_NAME, $inline.getAttribute(LINK_HREF_ATTR_NAME) ?? '');
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const setInlineFormat = ($n, formatName, shouldEnable) => {
|
|
175
|
+
if (shouldEnable) {
|
|
176
|
+
if (formatName === 'c' || isFormatName($n, 'c')) {
|
|
177
|
+
$n.className = '';
|
|
178
|
+
}
|
|
179
|
+
if (formatName === 'l' || isFormatName($n, 'l')) {
|
|
180
|
+
$n.className = '';
|
|
181
|
+
$n.removeAttribute(LINK_HREF_ATTR_NAME);
|
|
182
|
+
}
|
|
183
|
+
$n.classList.add(formatName);
|
|
184
|
+
} else {
|
|
185
|
+
if (formatName === 'l') {
|
|
186
|
+
$n.removeAttribute(LINK_HREF_ATTR_NAME);
|
|
187
|
+
}
|
|
188
|
+
$n.classList.remove(formatName);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const setAllInlineFormat = ($a, $b, formatName, shouldEnable) => {
|
|
192
|
+
const aBlock = getParentTextBlock($a);
|
|
193
|
+
const bBlock = getParentTextBlock($b);
|
|
194
|
+
let $currentBlock = aBlock;
|
|
195
|
+
let $prevBlock = null;
|
|
196
|
+
do {
|
|
197
|
+
let $n = $currentBlock === getParentTextBlock($a) ? $a : getFirstTextContent($currentBlock);
|
|
198
|
+
let $prev = $n;
|
|
199
|
+
do {
|
|
200
|
+
if (isInline($n)) {
|
|
201
|
+
setInlineFormat($n, formatName, shouldEnable);
|
|
202
|
+
}
|
|
203
|
+
$prev = $n;
|
|
204
|
+
$n = getNextSiblingTextContent($n);
|
|
205
|
+
} while ($n !== null && $prev !== $b);
|
|
206
|
+
$prevBlock = $currentBlock;
|
|
207
|
+
$currentBlock = getNextSiblingTextBlock($currentBlock);
|
|
208
|
+
} while ($currentBlock !== null && $prevBlock !== bBlock);
|
|
209
|
+
};
|
|
210
|
+
const toggleInlineFormat = ($n, formatName) => {
|
|
211
|
+
setInlineFormat($n, formatName, !isFormatName($n, formatName));
|
|
212
|
+
};
|
|
213
|
+
const areSameInlineFormat = ($a, $b) => {
|
|
214
|
+
if ($a.classList.length === 0 && $b.classList.length === 0) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if ($a.classList.length !== $b.classList.length) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if ($a.className === 'l') {
|
|
221
|
+
return $b.className === 'l' && $a.getAttribute(LINK_HREF_ATTR_NAME) === $b.getAttribute(LINK_HREF_ATTR_NAME);
|
|
222
|
+
}
|
|
223
|
+
for (let i = 0; i < $a.classList.length; i++) {
|
|
224
|
+
if (!$b.classList.contains($a.classList[i])) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
};
|
|
230
|
+
const createInlineWithText = (data, doc) => {
|
|
231
|
+
const $res = doc.createElement('SPAN');
|
|
232
|
+
$res.append(createTextNode(data, doc));
|
|
233
|
+
return $res;
|
|
234
|
+
};
|
|
235
|
+
const createInlineWithTextOfType = ($text, $source) => {
|
|
236
|
+
const $res = $source.ownerDocument.createElement($source.nodeName);
|
|
237
|
+
copyFormatName($source, $res);
|
|
238
|
+
$res.append($text);
|
|
239
|
+
return $res;
|
|
240
|
+
};
|
|
241
|
+
const createLink = (text, href, doc) => {
|
|
242
|
+
const $link = createInlineWithText(text, doc);
|
|
243
|
+
setInlineFormat($link, 'l', true);
|
|
244
|
+
$link.setAttribute(LINK_HREF_ATTR_NAME, href);
|
|
245
|
+
return $link;
|
|
246
|
+
};
|
|
247
|
+
const EMOJI_CHAR_ATTR_NAME = 'data-char';
|
|
248
|
+
const createEmoji = (emojiChar, baseUrl, doc) => {
|
|
249
|
+
const $emoji = doc.createElement('img');
|
|
250
|
+
$emoji.setAttribute(EMOJI_CHAR_ATTR_NAME, emojiChar);
|
|
251
|
+
$emoji.setAttribute('src', getEmojiUrl(baseUrl, emojiChar));
|
|
252
|
+
$emoji.classList.add('e');
|
|
253
|
+
return $emoji;
|
|
254
|
+
};
|
|
255
|
+
const PARAGRAPH_CLASSNAME = 'p';
|
|
256
|
+
const createActuallyEmptyParagraph = doc => {
|
|
257
|
+
const $p = doc.createElement('p');
|
|
258
|
+
$p.classList.add(PARAGRAPH_CLASSNAME);
|
|
259
|
+
return $p;
|
|
260
|
+
};
|
|
261
|
+
const createEmptyParagraph = doc => {
|
|
262
|
+
const res = createActuallyEmptyParagraph(doc);
|
|
263
|
+
res.append(createEmptyInline(doc));
|
|
264
|
+
return res;
|
|
265
|
+
};
|
|
266
|
+
const createParagraphWithChildren = (children, doc) => {
|
|
267
|
+
if (children.length === 0) {
|
|
268
|
+
return createEmptyParagraph(doc);
|
|
269
|
+
}
|
|
270
|
+
const $p = createActuallyEmptyParagraph(doc);
|
|
271
|
+
$p.append(...children);
|
|
272
|
+
return $p;
|
|
273
|
+
};
|
|
274
|
+
const ULI_CLASSNAME = 'uli';
|
|
275
|
+
const OLI_CLASSNAME = 'oli';
|
|
276
|
+
const createActuallyEmptyListItem = (isOrdered, listLevel, doc) => {
|
|
277
|
+
const $li = doc.createElement('p');
|
|
278
|
+
$li.className = isOrdered ? OLI_CLASSNAME : ULI_CLASSNAME;
|
|
279
|
+
setListItemLevel($li, listLevel);
|
|
280
|
+
return $li;
|
|
281
|
+
};
|
|
282
|
+
const createEmptyListItem = (isOrdered, listLevel, doc) => {
|
|
283
|
+
const $li = createActuallyEmptyListItem(isOrdered, listLevel, doc);
|
|
284
|
+
$li.append(createEmptyInline(doc));
|
|
285
|
+
return $li;
|
|
286
|
+
};
|
|
287
|
+
const setListItemOrderedType = ($li, isOrdered) => {
|
|
288
|
+
if (isOrdered) {
|
|
289
|
+
$li.classList.remove(ULI_CLASSNAME);
|
|
290
|
+
$li.classList.add(OLI_CLASSNAME);
|
|
291
|
+
} else {
|
|
292
|
+
$li.classList.remove(OLI_CLASSNAME);
|
|
293
|
+
$li.classList.add(ULI_CLASSNAME);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const convertToListItem = ($p, isOrdered) => {
|
|
297
|
+
$p.classList.remove(PARAGRAPH_CLASSNAME);
|
|
298
|
+
if (isOrdered) {
|
|
299
|
+
$p.classList.add(OLI_CLASSNAME);
|
|
300
|
+
} else {
|
|
301
|
+
$p.classList.add(ULI_CLASSNAME);
|
|
302
|
+
}
|
|
303
|
+
const $li = $p;
|
|
304
|
+
setListItemLevel($li, 0);
|
|
305
|
+
return $li;
|
|
306
|
+
};
|
|
307
|
+
const convertToParagraph = $li => {
|
|
308
|
+
removeListItemLevel($li);
|
|
309
|
+
$li.classList.remove(ULI_CLASSNAME, OLI_CLASSNAME);
|
|
310
|
+
$li.classList.add(PARAGRAPH_CLASSNAME);
|
|
311
|
+
ensureListItemLevelsAreCorrect($li);
|
|
312
|
+
return $li;
|
|
313
|
+
};
|
|
314
|
+
const createListItemWithChildren = (children, isOrdered, listLevel, doc) => {
|
|
315
|
+
if (children.length === 0) {
|
|
316
|
+
return createEmptyListItem(isOrdered, listLevel, doc);
|
|
317
|
+
}
|
|
318
|
+
const $li = createActuallyEmptyListItem(isOrdered, listLevel, doc);
|
|
319
|
+
$li.append(...children);
|
|
320
|
+
return $li;
|
|
321
|
+
};
|
|
322
|
+
const getParentTextBlock = $node => {
|
|
323
|
+
const $inline = isTextNode($node) ? getParentInline($node) : $node;
|
|
324
|
+
const $p = $inline.parentNode;
|
|
325
|
+
assertTextBlock($p);
|
|
326
|
+
return $p;
|
|
327
|
+
};
|
|
328
|
+
const childIndexOf = ($parent, $child) => {
|
|
329
|
+
return Array.prototype.indexOf.call($parent.childNodes, $child);
|
|
330
|
+
};
|
|
331
|
+
const getChildByIndex = ($parent, index) => {
|
|
332
|
+
const children = $parent.childNodes;
|
|
333
|
+
if (index < 0 || index >= children.length) {
|
|
334
|
+
throw new Error('Invalid index');
|
|
335
|
+
}
|
|
336
|
+
const $ch = index >= children.length ? children[children.length - 1] : children[index];
|
|
337
|
+
return $ch;
|
|
338
|
+
};
|
|
339
|
+
const afterLastChildIndex = $parent => {
|
|
340
|
+
return $parent.childNodes.length;
|
|
341
|
+
};
|
|
342
|
+
const isAfterLastChildIndex = ($parent, index) => {
|
|
343
|
+
return index >= $parent.childNodes.length;
|
|
344
|
+
};
|
|
345
|
+
const getLastChild = n => n.lastChild;
|
|
346
|
+
const getFirstChild = n => n.firstChild;
|
|
347
|
+
const getPrevSibling = n => n.previousSibling;
|
|
348
|
+
const getNextSibling = n => n.nextSibling;
|
|
349
|
+
const getSiblingTextBlock = getSibling => $node => {
|
|
350
|
+
const $sib = getSibling($node);
|
|
351
|
+
if ($sib !== null) {
|
|
352
|
+
assertTextBlock($sib);
|
|
353
|
+
return $sib;
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
};
|
|
357
|
+
const getPrevSiblingTextBlock = getSiblingTextBlock(getPrevSibling);
|
|
358
|
+
const getNextSiblingTextBlock = getSiblingTextBlock(getNextSibling);
|
|
359
|
+
const getPrevSiblingTextContent = ($node, ensureValid) => {
|
|
360
|
+
const $sib = $node.previousSibling;
|
|
361
|
+
if ($sib === null && ensureValid === true) {
|
|
362
|
+
const $currentBlock = getParentTextBlock($node);
|
|
363
|
+
const $prevBlock = getPrevSiblingTextBlock($currentBlock);
|
|
364
|
+
if ($prevBlock === null) {
|
|
365
|
+
$currentBlock.prepend(createEmptyInline($node.ownerDocument));
|
|
366
|
+
return getFirstTextContent($currentBlock);
|
|
367
|
+
}
|
|
368
|
+
return getLastTextContent($prevBlock);
|
|
369
|
+
}
|
|
370
|
+
if ($sib !== null) {
|
|
371
|
+
assertTextContent($sib);
|
|
372
|
+
}
|
|
373
|
+
return $sib;
|
|
374
|
+
};
|
|
375
|
+
const getNextSiblingTextContent = ($node, ensureValid) => {
|
|
376
|
+
const $sib = $node.nextSibling;
|
|
377
|
+
if ($sib === null && ensureValid === true) {
|
|
378
|
+
const $currentBlock = getParentTextBlock($node);
|
|
379
|
+
const $nextBlock = getNextSiblingTextBlock($currentBlock);
|
|
380
|
+
if ($nextBlock === null) {
|
|
381
|
+
$currentBlock.append(createEmptyInline($node.ownerDocument));
|
|
382
|
+
return getLastTextContent($currentBlock);
|
|
383
|
+
}
|
|
384
|
+
return getFirstTextContent($nextBlock);
|
|
385
|
+
}
|
|
386
|
+
if ($sib !== null) {
|
|
387
|
+
assertTextContent($sib);
|
|
388
|
+
}
|
|
389
|
+
return $sib;
|
|
390
|
+
};
|
|
391
|
+
const createCollapsedRange = cursor => {
|
|
392
|
+
const {
|
|
393
|
+
node,
|
|
394
|
+
offset
|
|
395
|
+
} = cursorToNodeWithOffset(cursor);
|
|
396
|
+
return {
|
|
397
|
+
startContainer: node,
|
|
398
|
+
startOffset: offset,
|
|
399
|
+
endContainer: node,
|
|
400
|
+
endOffset: offset
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
const createSpanningRange = (aCursor, bCursor) => {
|
|
404
|
+
const {
|
|
405
|
+
node: startContainer,
|
|
406
|
+
offset: startOffset
|
|
407
|
+
} = cursorToNodeWithOffset(aCursor);
|
|
408
|
+
const {
|
|
409
|
+
node: endContainer,
|
|
410
|
+
offset: endOffset
|
|
411
|
+
} = cursorToNodeWithOffset(bCursor);
|
|
412
|
+
return {
|
|
413
|
+
startContainer,
|
|
414
|
+
startOffset,
|
|
415
|
+
endContainer,
|
|
416
|
+
endOffset
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
const removePrevSiblings = $node => {
|
|
420
|
+
let $n;
|
|
421
|
+
while (($n = $node.previousSibling) !== null) {
|
|
422
|
+
$n.remove();
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
const removeNextSiblings = $node => {
|
|
426
|
+
let $n;
|
|
427
|
+
while (($n = $node.nextSibling) !== null) {
|
|
428
|
+
$n.remove();
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const removeSiblingsBetween = ($a, $b) => {
|
|
432
|
+
let $n;
|
|
433
|
+
while (($n = $a.nextSibling) !== $b) {
|
|
434
|
+
assertNonNull($n);
|
|
435
|
+
$n.remove();
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const getCommonParent = ($n0, $n1) => {
|
|
439
|
+
let $n = $n0;
|
|
440
|
+
while (!$n.contains($n1)) {
|
|
441
|
+
$n = $n.parentNode;
|
|
442
|
+
}
|
|
443
|
+
return $n;
|
|
444
|
+
};
|
|
445
|
+
const removeNodesBetween = (aCursor, bCursor) => {
|
|
446
|
+
if (aCursor.$inline === bCursor.$inline) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const commonParent = getCommonParent(aCursor.$inline, bCursor.$inline);
|
|
450
|
+
let $cpChildA = aCursor.$inline;
|
|
451
|
+
let $cpChildB = bCursor.$inline;
|
|
452
|
+
while ($cpChildA.parentNode !== commonParent) {
|
|
453
|
+
removeNextSiblings($cpChildA);
|
|
454
|
+
$cpChildA = $cpChildA.parentNode;
|
|
455
|
+
}
|
|
456
|
+
while ($cpChildB.parentNode !== commonParent) {
|
|
457
|
+
removePrevSiblings($cpChildB);
|
|
458
|
+
$cpChildB = $cpChildB.parentNode;
|
|
459
|
+
}
|
|
460
|
+
removeSiblingsBetween($cpChildA, $cpChildB);
|
|
461
|
+
};
|
|
462
|
+
const ensureCorrectTextBlockIfEmpty = $node => {
|
|
463
|
+
if (!$node.hasChildNodes()) {
|
|
464
|
+
$node.append(createEmptyInline($node.ownerDocument));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const children = $node.childNodes;
|
|
468
|
+
if (children.length === 1) {
|
|
469
|
+
const $firstChild = children[0];
|
|
470
|
+
assertTextContent($firstChild);
|
|
471
|
+
if (isInline($firstChild)) {
|
|
472
|
+
if ($firstChild.childNodes.length > 1 || !isTextNode($firstChild.firstChild)) {
|
|
473
|
+
$node.replaceChildren(createEmptyInline($node.ownerDocument));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const getChildText = $node => {
|
|
479
|
+
if ($node.childNodes.length !== 1) {
|
|
480
|
+
throw new Error('Should have exact 1 child');
|
|
481
|
+
}
|
|
482
|
+
const $child = $node.firstChild;
|
|
483
|
+
assertTextNode($child);
|
|
484
|
+
return $child;
|
|
485
|
+
};
|
|
486
|
+
const getChildTextContent = getFirstOrLastChild => $node => {
|
|
487
|
+
ensureCorrectTextBlockIfEmpty($node);
|
|
488
|
+
const $child = getFirstOrLastChild($node);
|
|
489
|
+
assertTextContent($child);
|
|
490
|
+
return $child;
|
|
491
|
+
};
|
|
492
|
+
const getLastTextContent = getChildTextContent(getLastChild);
|
|
493
|
+
const getFirstTextContent = getChildTextContent(getFirstChild);
|
|
494
|
+
const getFirstOrLastTextBlock = getFirstOrLastChild => $root => {
|
|
495
|
+
let $lc = getFirstOrLastChild($root);
|
|
496
|
+
if ($lc === null) {
|
|
497
|
+
$lc = createEmptyParagraph($root.ownerDocument);
|
|
498
|
+
$root.append($lc);
|
|
499
|
+
}
|
|
500
|
+
assertTextBlock($lc);
|
|
501
|
+
return $lc;
|
|
502
|
+
};
|
|
503
|
+
const getFirstTextBlock = getFirstOrLastTextBlock(getFirstChild);
|
|
504
|
+
const getLastTextBlock = getFirstOrLastTextBlock(getLastChild);
|
|
505
|
+
const mergeSiblingTextNodes = ($a, aCursor, bCursor) => {
|
|
506
|
+
const $b = $a.nextSibling;
|
|
507
|
+
if ($b === null) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
assertTextNode($b);
|
|
511
|
+
const aContent = getTextValue($a);
|
|
512
|
+
const bContent = getTextValue($b);
|
|
513
|
+
const aContentLength = isEmptyText(aContent) ? 0 : aContent.length;
|
|
514
|
+
const bContentLength = isEmptyText(bContent) ? 0 : bContent.length;
|
|
515
|
+
let resultText = (isEmptyText(aContent) ? '' : aContent) + (isEmptyText(bContent) ? '' : bContent);
|
|
516
|
+
const isEmptyResultText = resultText.length === 0;
|
|
517
|
+
if (aCursor.$text === $a) {
|
|
518
|
+
aCursor.offset = isEmptyResultText ? 1 : aCursor.offset;
|
|
519
|
+
}
|
|
520
|
+
if (aCursor.$text === $b) {
|
|
521
|
+
aCursor.$text = $a;
|
|
522
|
+
aCursor.offset = isEmptyResultText ? 1 : aContentLength + (bContentLength === 0 ? 0 : aCursor.offset);
|
|
523
|
+
}
|
|
524
|
+
if (bCursor != null) {
|
|
525
|
+
if (bCursor.$text === $a) {
|
|
526
|
+
bCursor.offset = isEmptyResultText ? 1 : bCursor.offset;
|
|
527
|
+
}
|
|
528
|
+
if (bCursor.$text === $b) {
|
|
529
|
+
bCursor.$text = $a;
|
|
530
|
+
bCursor.offset = isEmptyResultText ? 1 : aContentLength + (bContentLength === 0 ? 0 : bCursor.offset);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (isEmptyResultText) {
|
|
534
|
+
resultText = TEXT_EMPTY_DATA;
|
|
535
|
+
}
|
|
536
|
+
$a.nodeValue = resultText;
|
|
537
|
+
$b.remove();
|
|
538
|
+
mergeSiblingTextNodes($a, aCursor);
|
|
539
|
+
};
|
|
540
|
+
const mergeNextTextContentSibling = ($a, aCursor, bCursor) => {
|
|
541
|
+
const $b = $a.nextSibling;
|
|
542
|
+
if ($b === null) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
assertTextContent($b);
|
|
546
|
+
if (isInline($a) && isInline($b)) {
|
|
547
|
+
const $aText = getChildText($a);
|
|
548
|
+
const $bText = getChildText($b);
|
|
549
|
+
if (areSameInlineFormat($a, $b) || isEmptyTextNode($aText) || isEmptyTextNode($bText)) {
|
|
550
|
+
$a.append($bText);
|
|
551
|
+
$b.remove();
|
|
552
|
+
mergeSiblingTextNodes($aText, aCursor, bCursor);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const mergePrevTextContentSibling = ($b, aCursor, bCursor) => {
|
|
557
|
+
const $a = $b.previousSibling;
|
|
558
|
+
if ($a === null) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
assertTextContent($a);
|
|
562
|
+
if (isInline($a) && isInline($b)) {
|
|
563
|
+
const $aText = getChildText($a);
|
|
564
|
+
const $bText = getChildText($b);
|
|
565
|
+
if (areSameInlineFormat($a, $b) || isEmptyTextNode($aText) || isEmptyTextNode($bText)) {
|
|
566
|
+
$a.append($bText);
|
|
567
|
+
$b.remove();
|
|
568
|
+
mergeSiblingTextNodes($aText, aCursor, bCursor);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
const mergeTextContentBetween = ($a, $b, aCursor, bCursor) => {
|
|
573
|
+
const $sib = getNextSiblingTextContent($a);
|
|
574
|
+
if ($sib === null) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if ($a === $b || $sib === $b) {
|
|
578
|
+
mergeNextTextContentSibling($b, aCursor, bCursor);
|
|
579
|
+
mergeNextTextContentSibling($a, aCursor, bCursor);
|
|
580
|
+
mergePrevTextContentSibling($a, aCursor, bCursor);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
mergeNextTextContentSibling($a, aCursor, bCursor);
|
|
584
|
+
const $newSib = getNextSiblingTextContent($a);
|
|
585
|
+
assertNonNull($newSib);
|
|
586
|
+
if ($newSib === $sib) {
|
|
587
|
+
mergePrevTextContentSibling($a, aCursor, bCursor);
|
|
588
|
+
mergeTextContentBetween($sib, $b, aCursor, bCursor);
|
|
589
|
+
} else {
|
|
590
|
+
mergeTextContentBetween($a, $b, aCursor, bCursor);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
const mergeNextTextBlock = ($a, aCursor, bCursor) => {
|
|
594
|
+
const $b = $a.nextSibling;
|
|
595
|
+
if ($b === null) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
assertTextBlock($b);
|
|
599
|
+
const $lastChild = getLastChild($a);
|
|
600
|
+
if ($b.hasChildNodes()) {
|
|
601
|
+
$a.append(...Array.from($b.childNodes));
|
|
602
|
+
}
|
|
603
|
+
$b.remove();
|
|
604
|
+
if ($lastChild !== null) {
|
|
605
|
+
assertTextContent($lastChild);
|
|
606
|
+
mergeNextTextContentSibling($lastChild, aCursor, bCursor);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
const sliceChildren = ($parent, index) => {
|
|
610
|
+
if (index < 0) {
|
|
611
|
+
throw new Error('Not found');
|
|
612
|
+
}
|
|
613
|
+
const children = Array.from($parent.childNodes);
|
|
614
|
+
if (index === 0) {
|
|
615
|
+
return children;
|
|
616
|
+
}
|
|
617
|
+
return children.slice(index);
|
|
618
|
+
};
|
|
619
|
+
const splitTextBlock = ($a, childIndex) => {
|
|
620
|
+
const bChildren = sliceChildren($a, childIndex);
|
|
621
|
+
const $b = isListItem($a) ? createListItemWithChildren(bChildren, isOrderedListItem($a), getListItemLevel($a), $a.ownerDocument) : createParagraphWithChildren(bChildren, $a.ownerDocument);
|
|
622
|
+
ensureCorrectTextBlockIfEmpty($a);
|
|
623
|
+
ensureCorrectTextBlockIfEmpty($b);
|
|
624
|
+
$a.after($b);
|
|
625
|
+
return [$a, $b];
|
|
626
|
+
};
|
|
627
|
+
const splitTextNode = ($aText, offset) => {
|
|
628
|
+
const content = getTextValue($aText);
|
|
629
|
+
const aContent = content.substring(0, offset);
|
|
630
|
+
const bContent = content.substring(offset);
|
|
631
|
+
let $bText = null;
|
|
632
|
+
if (aContent.length > 0 && bContent.length > 0) {
|
|
633
|
+
$bText = createTextNode(bContent, $aText.ownerDocument);
|
|
634
|
+
$aText.nodeValue = aContent;
|
|
635
|
+
}
|
|
636
|
+
const $aInline = getParentInline($aText);
|
|
637
|
+
let $bInline = null;
|
|
638
|
+
if ($bText !== null) {
|
|
639
|
+
$bInline = createInlineWithTextOfType($bText, $aInline);
|
|
640
|
+
$aInline.after($bInline);
|
|
641
|
+
}
|
|
642
|
+
const $before = aContent.length === 0 ? getPrevSiblingTextContent($aInline) : $aInline;
|
|
643
|
+
const $after = $bInline ?? (aContent.length === 0 ? $aInline : getNextSiblingTextContent($aInline));
|
|
644
|
+
return [$before, $after];
|
|
645
|
+
};
|
|
646
|
+
const sliceTextNode = ($n, startOffset, endOffset) => {
|
|
647
|
+
const $after = splitTextNode($n, startOffset)[1];
|
|
648
|
+
assertInline($after);
|
|
649
|
+
const $before = splitTextNode(getChildText($after), endOffset - startOffset)[0];
|
|
650
|
+
assertEquals($before, $after);
|
|
651
|
+
return $after;
|
|
652
|
+
};
|
|
653
|
+
const splitTextNodeAndInsertEmptyInline = ($n, offset) => {
|
|
654
|
+
const [$before, $after] = splitTextNode($n, offset);
|
|
655
|
+
const $inline = createEmptyInline($n.ownerDocument);
|
|
656
|
+
copyFormatName($n, $inline);
|
|
657
|
+
if ($before !== null) {
|
|
658
|
+
$before.after($inline);
|
|
659
|
+
} else {
|
|
660
|
+
assertNonNull($after);
|
|
661
|
+
$after.before($inline);
|
|
662
|
+
}
|
|
663
|
+
return $inline;
|
|
664
|
+
};
|
|
665
|
+
export const formatInline = (formatType, range) => {
|
|
666
|
+
const aCursor = createIncomingCursorFromNodeWithOffset(range.startContainer, range.startOffset);
|
|
667
|
+
const bCursor = createIncomingCursorFromNodeWithOffset(range.endContainer, range.endOffset);
|
|
668
|
+
const formatName = FORMAT_TYPE_TO_NAME[formatType];
|
|
669
|
+
if (aCursor.$inline === bCursor.$inline && isTextNode(aCursor.$text)) {
|
|
670
|
+
const {
|
|
671
|
+
$text,
|
|
672
|
+
$inline,
|
|
673
|
+
offset: startOffset
|
|
674
|
+
} = aCursor;
|
|
675
|
+
const {
|
|
676
|
+
offset: endOffset
|
|
677
|
+
} = bCursor;
|
|
678
|
+
if (isEmptyText($text.nodeValue)) {
|
|
679
|
+
assertInline($inline);
|
|
680
|
+
toggleInlineFormat($inline, formatName);
|
|
681
|
+
return {
|
|
682
|
+
prevent: true,
|
|
683
|
+
range: createCollapsedRange(createEndCursorFromTextContent($inline))
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
if (startOffset === endOffset) {
|
|
687
|
+
const $newinline = splitTextNodeAndInsertEmptyInline($text, startOffset);
|
|
688
|
+
toggleInlineFormat($newinline, formatName);
|
|
689
|
+
return {
|
|
690
|
+
prevent: true,
|
|
691
|
+
range: createCollapsedRange(createEndCursorFromTextContent($newinline))
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const $newinline = sliceTextNode($text, startOffset, endOffset);
|
|
695
|
+
toggleInlineFormat($newinline, formatName);
|
|
696
|
+
const aTrackingCursor = createBeginCursorFromTextContent($newinline);
|
|
697
|
+
const bTrackingCursor = createEndCursorFromTextContent($newinline);
|
|
698
|
+
mergeNextTextContentSibling($newinline, aTrackingCursor, bTrackingCursor);
|
|
699
|
+
mergePrevTextContentSibling($newinline, aTrackingCursor, bTrackingCursor);
|
|
700
|
+
return {
|
|
701
|
+
prevent: true,
|
|
702
|
+
range: createSpanningRange(aTrackingCursor, bTrackingCursor)
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const areAllInline = isAllInsideFormatName(aCursor.$inline, bCursor.$inline, formatName);
|
|
706
|
+
let $aInline = aCursor.$inline;
|
|
707
|
+
let $bInline = bCursor.$inline;
|
|
708
|
+
if (isTextNode(aCursor.$text)) {
|
|
709
|
+
$aInline = splitTextNode(aCursor.$text, aCursor.offset)[1];
|
|
710
|
+
}
|
|
711
|
+
if (isTextNode(bCursor.$text)) {
|
|
712
|
+
$bInline = splitTextNode(bCursor.$text, bCursor.offset)[0];
|
|
713
|
+
}
|
|
714
|
+
if ($aInline === null) {
|
|
715
|
+
const aBlock = getNextSiblingTextBlock(getParentTextBlock(aCursor.$inline));
|
|
716
|
+
assertNonNull(aBlock);
|
|
717
|
+
$aInline = getFirstTextContent(aBlock);
|
|
718
|
+
}
|
|
719
|
+
if ($bInline === null) {
|
|
720
|
+
const bBlock = getPrevSiblingTextBlock(getParentTextBlock(bCursor.$inline));
|
|
721
|
+
assertNonNull(bBlock);
|
|
722
|
+
$bInline = getLastTextContent(bBlock);
|
|
723
|
+
}
|
|
724
|
+
setAllInlineFormat($aInline, $bInline, formatName, !areAllInline);
|
|
725
|
+
const aTrackingCursor = createBeginCursorFromTextContent($aInline);
|
|
726
|
+
const bTrackingCursor = createEndCursorFromTextContent($bInline);
|
|
727
|
+
mergeTextContentBetween($aInline, $bInline, aTrackingCursor, bTrackingCursor);
|
|
728
|
+
return {
|
|
729
|
+
prevent: true,
|
|
730
|
+
range: createSpanningRange(aTrackingCursor, bTrackingCursor)
|
|
731
|
+
};
|
|
732
|
+
};
|
|
733
|
+
const allSiblingsIncludingSatisfy = (aNode, bNode, func) => {
|
|
734
|
+
let currentNode = aNode;
|
|
735
|
+
const nextSib = bNode.nextSibling;
|
|
736
|
+
while (currentNode !== null && currentNode !== nextSib) {
|
|
737
|
+
const tempNext = currentNode.nextSibling;
|
|
738
|
+
if (!func(currentNode)) {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
currentNode = tempNext;
|
|
742
|
+
}
|
|
743
|
+
return true;
|
|
744
|
+
};
|
|
745
|
+
export const formatIndent = range => {
|
|
746
|
+
if (range.startContainer === range.endContainer && range.startOffset === range.endOffset && range.startOffset !== 0) {
|
|
747
|
+
return {
|
|
748
|
+
prevent: false
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
const aCursor = createIncomingCursorFromNodeWithOffset(range.startContainer, range.startOffset);
|
|
752
|
+
const bCursor = createIncomingCursorFromNodeWithOffset(range.endContainer, range.endOffset);
|
|
753
|
+
const aBlock = getParentTextBlock(aCursor.$inline);
|
|
754
|
+
const bBlock = getParentTextBlock(bCursor.$inline);
|
|
755
|
+
const $iterateUpToNode = getNextSiblingTextBlock(bBlock);
|
|
756
|
+
let $block = aBlock;
|
|
757
|
+
let $prevBlock = null;
|
|
758
|
+
while ($block !== null && $block !== $iterateUpToNode) {
|
|
759
|
+
if (isListItem($block) && !isListItemMarkedArBlock($block)) {
|
|
760
|
+
const $prev = $prevBlock ?? getPrevSiblingTextBlock($block);
|
|
761
|
+
const currentLevel = getListItemLevel($block);
|
|
762
|
+
if (isListItem($prev) && currentLevel <= getListItemLevel($prev)) {
|
|
763
|
+
setListItemLevel($block, currentLevel + 1);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
$prevBlock = $block;
|
|
767
|
+
$block = getNextSiblingTextBlock($block);
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
prevent: true,
|
|
771
|
+
range
|
|
772
|
+
};
|
|
773
|
+
};
|
|
774
|
+
const ensureListItemLevelsAreCorrect = $li => {
|
|
775
|
+
if ($li === null) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
let $block = getNextSiblingTextBlock($li);
|
|
779
|
+
let prevLevel = isListItem($li) ? getListItemLevel($li) : -1;
|
|
780
|
+
while ($block !== null && isListItem($block)) {
|
|
781
|
+
let currentLevel = getListItemLevel($block);
|
|
782
|
+
if (currentLevel <= prevLevel + 1) {
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
currentLevel = prevLevel + 1;
|
|
786
|
+
setListItemLevel($block, currentLevel);
|
|
787
|
+
prevLevel = currentLevel;
|
|
788
|
+
$block = getNextSiblingTextBlock($block);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
export const formatOutdent = range => {
|
|
792
|
+
if (range.startContainer === range.endContainer && range.startOffset === range.endOffset && range.startOffset !== 0) {
|
|
793
|
+
return {
|
|
794
|
+
prevent: false
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
const aCursor = createIncomingCursorFromNodeWithOffset(range.startContainer, range.startOffset);
|
|
798
|
+
const bCursor = createIncomingCursorFromNodeWithOffset(range.endContainer, range.endOffset);
|
|
799
|
+
const aBlock = getParentTextBlock(aCursor.$inline);
|
|
800
|
+
const bBlock = getParentTextBlock(bCursor.$inline);
|
|
801
|
+
const $iterateUpToNode = getNextSiblingTextBlock(bBlock);
|
|
802
|
+
let $block = aBlock;
|
|
803
|
+
let $lastListItem = null;
|
|
804
|
+
while ($block !== null && $block !== $iterateUpToNode) {
|
|
805
|
+
if (isListItem($block)) {
|
|
806
|
+
setListItemLevel($block, getListItemLevel($block) - 1);
|
|
807
|
+
$lastListItem = $block;
|
|
808
|
+
}
|
|
809
|
+
$block = getNextSiblingTextBlock($block);
|
|
810
|
+
}
|
|
811
|
+
if ($lastListItem !== null) {
|
|
812
|
+
ensureListItemLevelsAreCorrect($lastListItem);
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
prevent: true,
|
|
816
|
+
range
|
|
817
|
+
};
|
|
818
|
+
};
|
|
819
|
+
export const formatList = (isOrdered, range) => {
|
|
820
|
+
const aCursor = createIncomingCursorFromNodeWithOffset(range.startContainer, range.startOffset);
|
|
821
|
+
const bCursor = createIncomingCursorFromNodeWithOffset(range.endContainer, range.endOffset);
|
|
822
|
+
const aBlock = getParentTextBlock(aCursor.$inline);
|
|
823
|
+
const bBlock = getParentTextBlock(bCursor.$inline);
|
|
824
|
+
const isInsideSameListType = isAllInsideList(isOrdered, aCursor.$inline, bCursor.$inline);
|
|
825
|
+
if (isInsideSameListType) {
|
|
826
|
+
assertListItem(aBlock);
|
|
827
|
+
assertListItem(bBlock);
|
|
828
|
+
const $iterateUpToNode = getNextSiblingTextBlock(bBlock);
|
|
829
|
+
let $block = aBlock;
|
|
830
|
+
while ($block !== null && $block !== $iterateUpToNode) {
|
|
831
|
+
convertToParagraph($block);
|
|
832
|
+
$block = getNextSiblingTextBlock($block);
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
prevent: true,
|
|
836
|
+
range: createCollapsedRange(bCursor)
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
const $iterateUpToNode = getNextSiblingTextBlock(bBlock);
|
|
840
|
+
let $block = aBlock;
|
|
841
|
+
while ($block !== null && $block !== $iterateUpToNode) {
|
|
842
|
+
if (isParagraph($block)) {
|
|
843
|
+
convertToListItem($block, isOrdered);
|
|
844
|
+
} else {
|
|
845
|
+
assertListItem($block);
|
|
846
|
+
setListItemOrderedType($block, isOrdered);
|
|
847
|
+
}
|
|
848
|
+
$block = getNextSiblingTextBlock($block);
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
prevent: true,
|
|
852
|
+
range: createCollapsedRange(bCursor)
|
|
853
|
+
};
|
|
854
|
+
};
|
|
855
|
+
const createCursorFromTextNode = (node, offset) => {
|
|
856
|
+
return {
|
|
857
|
+
$text: node,
|
|
858
|
+
$inline: getParentInline(node),
|
|
859
|
+
offset: Math.min(node.length, offset)
|
|
860
|
+
};
|
|
861
|
+
};
|
|
862
|
+
const createBeginCursorFromTextContent = node => {
|
|
863
|
+
if (isInline(node)) {
|
|
864
|
+
const $text = getChildText(node);
|
|
865
|
+
const content = getTextValue($text);
|
|
866
|
+
return {
|
|
867
|
+
$inline: node,
|
|
868
|
+
$text,
|
|
869
|
+
offset: isEmptyText(content) ? content.length : 0
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
$text: null,
|
|
874
|
+
$inline: node,
|
|
875
|
+
offset: 0
|
|
876
|
+
};
|
|
877
|
+
};
|
|
878
|
+
const createEndCursorFromTextContent = node => {
|
|
879
|
+
if (isInline(node)) {
|
|
880
|
+
const $text = getChildText(node);
|
|
881
|
+
return {
|
|
882
|
+
$inline: node,
|
|
883
|
+
$text,
|
|
884
|
+
offset: $text.length
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
$text: null,
|
|
889
|
+
$inline: node,
|
|
890
|
+
offset: 0,
|
|
891
|
+
isAfterInline: true
|
|
892
|
+
};
|
|
893
|
+
};
|
|
894
|
+
const createIncomingCursorFromNodeWithOffset = (node, offset) => {
|
|
895
|
+
if (isTextNode(node)) {
|
|
896
|
+
return createCursorFromTextNode(node, offset);
|
|
897
|
+
}
|
|
898
|
+
if (isEmoji(node)) {
|
|
899
|
+
return {
|
|
900
|
+
$text: null,
|
|
901
|
+
$inline: node,
|
|
902
|
+
offset: 0
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
if (isTextBlock(node)) {
|
|
906
|
+
if (isAfterLastChildIndex(node, offset)) {
|
|
907
|
+
return createEndCursorFromTextContent(getLastTextContent(node));
|
|
908
|
+
}
|
|
909
|
+
return createIncomingCursorFromNodeWithOffset(getChildByIndex(node, offset), 0);
|
|
910
|
+
}
|
|
911
|
+
if (isInline(node)) {
|
|
912
|
+
return {
|
|
913
|
+
$inline: node,
|
|
914
|
+
$text: getChildText(node),
|
|
915
|
+
offset: 0
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
if (isRoot(node)) {
|
|
919
|
+
if (isAfterLastChildIndex(node, offset)) {
|
|
920
|
+
const $block = getLastTextBlock(node);
|
|
921
|
+
return createIncomingCursorFromNodeWithOffset($block, $block.childNodes.length);
|
|
922
|
+
}
|
|
923
|
+
return createIncomingCursorFromNodeWithOffset(getChildByIndex(node, offset), 0);
|
|
924
|
+
}
|
|
925
|
+
throw new Error('Should not happen');
|
|
926
|
+
};
|
|
927
|
+
const cursorToNodeWithOffset = _ref => {
|
|
928
|
+
let {
|
|
929
|
+
$text,
|
|
930
|
+
$inline,
|
|
931
|
+
offset,
|
|
932
|
+
isAfterInline
|
|
933
|
+
} = _ref;
|
|
934
|
+
if (isTextNode($text)) {
|
|
935
|
+
return {
|
|
936
|
+
node: $text,
|
|
937
|
+
offset
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
const $block = getParentTextBlock($inline);
|
|
941
|
+
const childIndex = childIndexOf($block, $inline) + (isAfterInline === true ? 1 : 0);
|
|
942
|
+
return {
|
|
943
|
+
node: $block,
|
|
944
|
+
offset: childIndex
|
|
945
|
+
};
|
|
946
|
+
};
|
|
947
|
+
const removeContentInRange = range => {
|
|
948
|
+
const aCursor = createIncomingCursorFromNodeWithOffset(range.startContainer, range.startOffset);
|
|
949
|
+
const bCursor = createIncomingCursorFromNodeWithOffset(range.endContainer, range.endOffset);
|
|
950
|
+
if (aCursor.$inline === bCursor.$inline && aCursor.offset === bCursor.offset && aCursor.isAfterInline === bCursor.isAfterInline) {
|
|
951
|
+
return aCursor;
|
|
952
|
+
}
|
|
953
|
+
if (aCursor.$text === bCursor.$text && isTextNode(aCursor.$text)) {
|
|
954
|
+
const {
|
|
955
|
+
$text,
|
|
956
|
+
$inline,
|
|
957
|
+
offset: startOffset
|
|
958
|
+
} = aCursor;
|
|
959
|
+
const {
|
|
960
|
+
offset: endOffset
|
|
961
|
+
} = bCursor;
|
|
962
|
+
const content = getTextValue($text);
|
|
963
|
+
const nextContent = content.substring(0, startOffset) + content.substring(endOffset);
|
|
964
|
+
$text.nodeValue = nextContent;
|
|
965
|
+
if (isEmptyText(nextContent)) {
|
|
966
|
+
const $nextSib = getNextSiblingTextContent($inline);
|
|
967
|
+
const $prevSib = getPrevSiblingTextContent($inline);
|
|
968
|
+
if ($prevSib === null && $nextSib === null) {
|
|
969
|
+
$text.nodeValue = TEXT_EMPTY_DATA;
|
|
970
|
+
return createEndCursorFromTextContent($inline);
|
|
971
|
+
}
|
|
972
|
+
$inline.remove();
|
|
973
|
+
if ($nextSib === null) {
|
|
974
|
+
assertNonNull($prevSib);
|
|
975
|
+
return createEndCursorFromTextContent($prevSib);
|
|
976
|
+
}
|
|
977
|
+
return createBeginCursorFromTextContent($nextSib);
|
|
978
|
+
}
|
|
979
|
+
return createCursorFromTextNode($text, startOffset);
|
|
980
|
+
}
|
|
981
|
+
removeNodesBetween(aCursor, bCursor);
|
|
982
|
+
if (isTextNode(aCursor.$text)) {
|
|
983
|
+
aCursor.$text.nodeValue = getTextValue(aCursor.$text).substring(0, aCursor.offset);
|
|
984
|
+
}
|
|
985
|
+
if (isTextNode(bCursor.$text)) {
|
|
986
|
+
bCursor.$text.nodeValue = getTextValue(bCursor.$text).substring(bCursor.offset);
|
|
987
|
+
}
|
|
988
|
+
const aBlock = getParentTextBlock(aCursor.$inline);
|
|
989
|
+
const bBlock = getParentTextBlock(bCursor.$inline);
|
|
990
|
+
if (aCursor.$inline !== bCursor.$inline && (isEmptyTextNode(bCursor.$text) || bCursor.$text === null && bCursor.isAfterInline === true)) {
|
|
991
|
+
bCursor.$inline.remove();
|
|
992
|
+
}
|
|
993
|
+
let trackingCursor;
|
|
994
|
+
if (isEmptyTextNode(aCursor.$text) || aCursor.$text === null && aCursor.isAfterInline !== true) {
|
|
995
|
+
const $sib = getNextSiblingTextContent(aCursor.$inline);
|
|
996
|
+
aCursor.$inline.remove();
|
|
997
|
+
if ($sib === null) {
|
|
998
|
+
trackingCursor = createEndCursorFromTextContent(getLastTextContent(aBlock));
|
|
999
|
+
} else {
|
|
1000
|
+
trackingCursor = createBeginCursorFromTextContent($sib);
|
|
1001
|
+
}
|
|
1002
|
+
} else {
|
|
1003
|
+
trackingCursor = createEndCursorFromTextContent(aCursor.$inline);
|
|
1004
|
+
}
|
|
1005
|
+
if (aBlock === bBlock) {
|
|
1006
|
+
mergeNextTextContentSibling(trackingCursor.$inline, trackingCursor);
|
|
1007
|
+
mergePrevTextContentSibling(trackingCursor.$inline, trackingCursor);
|
|
1008
|
+
} else if (getNextSiblingTextBlock(aBlock) === bBlock) {
|
|
1009
|
+
mergeNextTextBlock(aBlock, trackingCursor);
|
|
1010
|
+
}
|
|
1011
|
+
return trackingCursor;
|
|
1012
|
+
};
|
|
1013
|
+
export const deleteContentBackward = ($root, range) => {
|
|
1014
|
+
const {
|
|
1015
|
+
startContainer,
|
|
1016
|
+
endContainer,
|
|
1017
|
+
startOffset,
|
|
1018
|
+
endOffset
|
|
1019
|
+
} = range;
|
|
1020
|
+
if (startContainer === endContainer && startOffset + 1 === endOffset && isTextNode(startContainer)) {
|
|
1021
|
+
if (startContainer.length > 1) {
|
|
1022
|
+
return DEFAULT_ACTION_RESULT;
|
|
1023
|
+
}
|
|
1024
|
+
if (startContainer.length === 1 && startContainer.nodeValue !== TEXT_EMPTY_DATA) {
|
|
1025
|
+
startContainer.nodeValue = TEXT_EMPTY_DATA;
|
|
1026
|
+
return {
|
|
1027
|
+
prevent: true,
|
|
1028
|
+
range: {
|
|
1029
|
+
startContainer,
|
|
1030
|
+
startOffset: 1,
|
|
1031
|
+
endContainer: startContainer,
|
|
1032
|
+
endOffset: 1
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
const $inline = getParentInline(startContainer);
|
|
1037
|
+
const $block = getParentTextBlock($inline);
|
|
1038
|
+
const childIndex = childIndexOf($block, $inline);
|
|
1039
|
+
if (childIndex > 0) {
|
|
1040
|
+
const $prevSib = getChildByIndex($block, childIndex - 1);
|
|
1041
|
+
if (isInline($prevSib)) {
|
|
1042
|
+
const $text = getChildText($prevSib);
|
|
1043
|
+
return deleteContentBackward($root, {
|
|
1044
|
+
startContainer: $text,
|
|
1045
|
+
startOffset: $text.length - 1,
|
|
1046
|
+
endContainer,
|
|
1047
|
+
endOffset
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
return deleteContentBackward($root, {
|
|
1051
|
+
startContainer: $block,
|
|
1052
|
+
startOffset: childIndex - 1,
|
|
1053
|
+
endContainer,
|
|
1054
|
+
endOffset
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
const $prevBlock = getPrevSiblingTextBlock($block);
|
|
1058
|
+
if ($prevBlock !== null) {
|
|
1059
|
+
return deleteContentBackward($root, {
|
|
1060
|
+
startContainer: $prevBlock,
|
|
1061
|
+
startOffset: afterLastChildIndex($prevBlock),
|
|
1062
|
+
endContainer,
|
|
1063
|
+
endOffset
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
prevent: true,
|
|
1069
|
+
range: createCollapsedRange(removeContentInRange(range))
|
|
1070
|
+
};
|
|
1071
|
+
};
|
|
1072
|
+
export const insertLink = ($root, text, href, range) => {
|
|
1073
|
+
const cursor = removeContentInRange(range);
|
|
1074
|
+
const {
|
|
1075
|
+
$text,
|
|
1076
|
+
$inline,
|
|
1077
|
+
offset,
|
|
1078
|
+
isAfterInline: isAfterLastChild
|
|
1079
|
+
} = cursor;
|
|
1080
|
+
const $link = createLink(text, href, $root.ownerDocument);
|
|
1081
|
+
if (isTextNode($text)) {
|
|
1082
|
+
if (isEmptyText($text.nodeValue)) {
|
|
1083
|
+
getParentTextBlock($inline).replaceChild($link, $inline);
|
|
1084
|
+
} else {
|
|
1085
|
+
const [$before, $after] = splitTextNode($text, offset);
|
|
1086
|
+
if ($before === null) {
|
|
1087
|
+
assertNonNull($after);
|
|
1088
|
+
$after.before($link);
|
|
1089
|
+
} else {
|
|
1090
|
+
$before.after($link);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
} else if (isAfterLastChild === true) {
|
|
1094
|
+
$inline.after($link);
|
|
1095
|
+
} else {
|
|
1096
|
+
$inline.before($link);
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
prevent: true,
|
|
1100
|
+
range: createCollapsedRange(createEndCursorFromTextContent($link))
|
|
1101
|
+
};
|
|
1102
|
+
};
|
|
1103
|
+
export const insertLineBreak = range => {
|
|
1104
|
+
const cursor = removeContentInRange(range);
|
|
1105
|
+
const {
|
|
1106
|
+
$text,
|
|
1107
|
+
$inline,
|
|
1108
|
+
offset,
|
|
1109
|
+
isAfterInline: isAfterLastChild
|
|
1110
|
+
} = cursor;
|
|
1111
|
+
const $block = getParentTextBlock($inline);
|
|
1112
|
+
if (isListItem($block)) {
|
|
1113
|
+
if (isEmptyTextBlock($block)) {
|
|
1114
|
+
convertToParagraph($block);
|
|
1115
|
+
return {
|
|
1116
|
+
prevent: true,
|
|
1117
|
+
range: createCollapsedRange(createEndCursorFromTextContent(getLastTextContent($block)))
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
let splitIndex = 0;
|
|
1122
|
+
if (isTextNode($text)) {
|
|
1123
|
+
const [$before] = splitTextNode($text, offset);
|
|
1124
|
+
splitIndex = $before !== null ? childIndexOf($block, $before) + 1 : 0;
|
|
1125
|
+
} else {
|
|
1126
|
+
splitIndex = childIndexOf($block, $inline) + (isAfterLastChild === true ? 1 : 0);
|
|
1127
|
+
}
|
|
1128
|
+
const [_, $bBlock] = splitTextBlock($block, splitIndex);
|
|
1129
|
+
return {
|
|
1130
|
+
prevent: true,
|
|
1131
|
+
range: createCollapsedRange(createBeginCursorFromTextContent(getFirstTextContent($bBlock)))
|
|
1132
|
+
};
|
|
1133
|
+
};
|
|
1134
|
+
const insertEmoji = ($root, emojiChar, range) => {
|
|
1135
|
+
const cursor = removeContentInRange(range);
|
|
1136
|
+
const {
|
|
1137
|
+
$text,
|
|
1138
|
+
$inline,
|
|
1139
|
+
offset,
|
|
1140
|
+
isAfterInline
|
|
1141
|
+
} = cursor;
|
|
1142
|
+
const baseUrl = getEmojiBaseUrl($root);
|
|
1143
|
+
const $emoji = createEmoji(emojiChar, baseUrl, $root.ownerDocument);
|
|
1144
|
+
if (isTextNode($text)) {
|
|
1145
|
+
if (isEmptyText($text.nodeValue)) {
|
|
1146
|
+
getParentTextBlock($inline).replaceChild($emoji, $inline);
|
|
1147
|
+
} else {
|
|
1148
|
+
const [$before, $after] = splitTextNode($text, offset);
|
|
1149
|
+
if ($before === null) {
|
|
1150
|
+
assertNonNull($after);
|
|
1151
|
+
$after.before($emoji);
|
|
1152
|
+
} else {
|
|
1153
|
+
$before.after($emoji);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
} else if (isAfterInline === true) {
|
|
1157
|
+
$inline.after($emoji);
|
|
1158
|
+
} else {
|
|
1159
|
+
$inline.before($emoji);
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
prevent: true,
|
|
1163
|
+
range: createCollapsedRange(createEndCursorFromTextContent($emoji))
|
|
1164
|
+
};
|
|
1165
|
+
};
|
|
1166
|
+
export const insertText = ($root, data, range) => {
|
|
1167
|
+
if (data !== null && isEmojiString(data)) {
|
|
1168
|
+
return insertEmoji($root, data, range);
|
|
1169
|
+
}
|
|
1170
|
+
if (range.startContainer === range.endContainer && range.startOffset === range.endOffset && isTextNode(range.startContainer) && !isEmptyText(range.startContainer.nodeValue)) {
|
|
1171
|
+
const $inline = getParentInline(range.startContainer);
|
|
1172
|
+
const isHotTextWhitespace = range.startOffset === range.startContainer.length && data === TEXT_WHITESPACE && (isFormatCodetag($inline) || isFormatLink($inline));
|
|
1173
|
+
if (!isHotTextWhitespace) {
|
|
1174
|
+
return DEFAULT_ACTION_RESULT;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const cursor = removeContentInRange(range);
|
|
1178
|
+
if (data === null) {
|
|
1179
|
+
return {
|
|
1180
|
+
prevent: true,
|
|
1181
|
+
range: createCollapsedRange(cursor)
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
const {
|
|
1185
|
+
$text,
|
|
1186
|
+
$inline,
|
|
1187
|
+
offset,
|
|
1188
|
+
isAfterInline
|
|
1189
|
+
} = cursor;
|
|
1190
|
+
if ($text === null && isEmoji($inline)) {
|
|
1191
|
+
const $newInline = createInlineWithText(data, $root.ownerDocument);
|
|
1192
|
+
if (isAfterInline === true) {
|
|
1193
|
+
$inline.after($newInline);
|
|
1194
|
+
} else {
|
|
1195
|
+
$inline.before($newInline);
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
prevent: true,
|
|
1199
|
+
range: createCollapsedRange(createEndCursorFromTextContent($newInline))
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
if (isTextNode($text)) {
|
|
1203
|
+
assertInline($inline);
|
|
1204
|
+
if (isEmptyText($text.nodeValue)) {
|
|
1205
|
+
$text.nodeValue = data;
|
|
1206
|
+
return {
|
|
1207
|
+
prevent: true,
|
|
1208
|
+
range: createCollapsedRange(createCursorFromTextNode($text, data.length))
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
if (offset === $text.length && data === TEXT_WHITESPACE) {
|
|
1212
|
+
if (isFormatLink($inline) || isFormatCodetag($inline)) {
|
|
1213
|
+
const $newinline = createInlineWithText(data, $root.ownerDocument);
|
|
1214
|
+
$inline.after($newinline);
|
|
1215
|
+
return {
|
|
1216
|
+
prevent: true,
|
|
1217
|
+
range: createCollapsedRange(createEndCursorFromTextContent($newinline))
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
const content = $text.nodeValue;
|
|
1222
|
+
$text.nodeValue = content.substring(0, offset) + data + content.substring(offset);
|
|
1223
|
+
return {
|
|
1224
|
+
prevent: true,
|
|
1225
|
+
range: createCollapsedRange(createCursorFromTextNode($text, offset + data.length))
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
return {
|
|
1229
|
+
prevent: true,
|
|
1230
|
+
range: createCollapsedRange(cursor)
|
|
1231
|
+
};
|
|
1232
|
+
};
|
|
1233
|
+
export const insertFromPaste = (data, range, visitor) => {
|
|
1234
|
+
const cursor = removeContentInRange(range);
|
|
1235
|
+
const {
|
|
1236
|
+
$text,
|
|
1237
|
+
$inline,
|
|
1238
|
+
offset,
|
|
1239
|
+
isAfterInline
|
|
1240
|
+
} = cursor;
|
|
1241
|
+
const $mdFragment = parseMarkdown(data, visitor);
|
|
1242
|
+
if ($mdFragment.childNodes.length === 0) {
|
|
1243
|
+
return {
|
|
1244
|
+
prevent: true,
|
|
1245
|
+
range
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
const shouldSpreadNodesIntoBlock = $mdFragment.childNodes.length === 1;
|
|
1249
|
+
if (shouldSpreadNodesIntoBlock) {
|
|
1250
|
+
const $block = getParentTextBlock($inline);
|
|
1251
|
+
const $fragment = document.createDocumentFragment();
|
|
1252
|
+
$fragment.append(...Array.from($mdFragment.childNodes[0].childNodes));
|
|
1253
|
+
const $pasteLastChild = $fragment.lastChild;
|
|
1254
|
+
if ($pasteLastChild !== null) {
|
|
1255
|
+
assertTextContent($pasteLastChild);
|
|
1256
|
+
}
|
|
1257
|
+
if (isTextNode($text)) {
|
|
1258
|
+
if (isEmptyText($text.nodeValue)) {
|
|
1259
|
+
$block.replaceChild($fragment, $inline);
|
|
1260
|
+
} else {
|
|
1261
|
+
const [$before, $after] = splitTextNode($text, offset);
|
|
1262
|
+
if ($before === null) {
|
|
1263
|
+
assertNonNull($after);
|
|
1264
|
+
$after.before($fragment);
|
|
1265
|
+
} else {
|
|
1266
|
+
$before.after($fragment);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
} else if (isAfterInline === true) {
|
|
1270
|
+
$inline.after($fragment);
|
|
1271
|
+
} else {
|
|
1272
|
+
$inline.before($fragment);
|
|
1273
|
+
}
|
|
1274
|
+
return {
|
|
1275
|
+
prevent: true,
|
|
1276
|
+
range: createCollapsedRange(createEndCursorFromTextContent($pasteLastChild ?? getLastTextContent($block)))
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
const $block = getParentTextBlock($inline);
|
|
1280
|
+
let splitIndex = childIndexOf($block, $inline) + (isAfterInline === true ? 1 : 0);
|
|
1281
|
+
if (isTextNode($text)) {
|
|
1282
|
+
if (isEmptyText($text.nodeValue)) {
|
|
1283
|
+
$inline.remove();
|
|
1284
|
+
} else {
|
|
1285
|
+
const [$before] = splitTextNode($text, offset);
|
|
1286
|
+
if ($before !== null) {
|
|
1287
|
+
splitIndex++;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const [$aBlock, $bBlock] = splitTextBlock($block, splitIndex);
|
|
1292
|
+
const $root = $aBlock.parentElement;
|
|
1293
|
+
const $lastBlock = $mdFragment.lastChild;
|
|
1294
|
+
assertTextBlock($lastBlock);
|
|
1295
|
+
if (isEmptyTextBlock($aBlock)) {
|
|
1296
|
+
$root.replaceChild($mdFragment, $aBlock);
|
|
1297
|
+
if (isEmptyTextBlock($bBlock)) {
|
|
1298
|
+
$bBlock.remove();
|
|
1299
|
+
}
|
|
1300
|
+
} else if (isEmptyTextBlock($bBlock)) {
|
|
1301
|
+
$root.replaceChild($mdFragment, $bBlock);
|
|
1302
|
+
} else {
|
|
1303
|
+
$aBlock.after($mdFragment);
|
|
1304
|
+
}
|
|
1305
|
+
return {
|
|
1306
|
+
prevent: true,
|
|
1307
|
+
range: createCollapsedRange(createEndCursorFromTextContent(getLastTextContent($lastBlock)))
|
|
1308
|
+
};
|
|
1309
|
+
};
|
|
1310
|
+
const DEFAULT_ACTION_RESULT = {
|
|
1311
|
+
prevent: false
|
|
1312
|
+
};
|
|
1313
|
+
export const handleEmojiMousedown = $n => {
|
|
1314
|
+
if (isEmoji($n)) {
|
|
1315
|
+
return {
|
|
1316
|
+
prevent: true,
|
|
1317
|
+
range: createCollapsedRange(createEndCursorFromTextContent($n))
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
return DEFAULT_ACTION_RESULT;
|
|
1321
|
+
};
|
|
1322
|
+
export const getBeginRange = $root => {
|
|
1323
|
+
const $block = getFirstTextBlock($root);
|
|
1324
|
+
const $inline = getFirstTextContent($block);
|
|
1325
|
+
return createCollapsedRange(createBeginCursorFromTextContent($inline));
|
|
1326
|
+
};
|
|
1327
|
+
export const getEndRange = $root => {
|
|
1328
|
+
const $block = getLastTextBlock($root);
|
|
1329
|
+
const $inline = getLastTextContent($block);
|
|
1330
|
+
return createCollapsedRange(createEndCursorFromTextContent($inline));
|
|
1331
|
+
};
|
|
1332
|
+
export const getSelectionInfo = range => {
|
|
1333
|
+
const aCursor = createIncomingCursorFromNodeWithOffset(range.startContainer, range.startOffset);
|
|
1334
|
+
const bCursor = createIncomingCursorFromNodeWithOffset(range.endContainer, range.endOffset);
|
|
1335
|
+
if (aCursor.$inline === bCursor.$inline) {
|
|
1336
|
+
const {
|
|
1337
|
+
$text,
|
|
1338
|
+
$inline
|
|
1339
|
+
} = aCursor;
|
|
1340
|
+
if ($text !== null || isInline($inline)) {
|
|
1341
|
+
assertInline($inline);
|
|
1342
|
+
return {
|
|
1343
|
+
bold: isFormatBold($inline),
|
|
1344
|
+
italic: isFormatItalic($inline),
|
|
1345
|
+
strikethrough: isFormatStrikethrough($inline),
|
|
1346
|
+
codetag: isFormatCodetag($inline),
|
|
1347
|
+
link: isFormatLink($inline),
|
|
1348
|
+
olist: isInsideList(true, $inline),
|
|
1349
|
+
ulist: isInsideList(false, $inline)
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
return {
|
|
1353
|
+
italic: false,
|
|
1354
|
+
bold: false,
|
|
1355
|
+
strikethrough: false,
|
|
1356
|
+
codetag: false,
|
|
1357
|
+
link: false,
|
|
1358
|
+
olist: false,
|
|
1359
|
+
ulist: false
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
bold: isAllInsideBold(aCursor.$inline, bCursor.$inline),
|
|
1364
|
+
italic: isAllInsideItalic(aCursor.$inline, bCursor.$inline),
|
|
1365
|
+
strikethrough: isAllInsideStrikethrough(aCursor.$inline, bCursor.$inline),
|
|
1366
|
+
codetag: isAllInsideCodetag(aCursor.$inline, bCursor.$inline),
|
|
1367
|
+
link: isAllInsideLink(aCursor.$inline, bCursor.$inline),
|
|
1368
|
+
olist: isAllInsideList(true, aCursor.$inline, bCursor.$inline),
|
|
1369
|
+
ulist: isAllInsideList(false, aCursor.$inline, bCursor.$inline)
|
|
1370
|
+
};
|
|
1371
|
+
};
|
|
1372
|
+
export const isSelectionEqual = (a, b) => {
|
|
1373
|
+
return a === b || a !== null && b !== null && a.bold === b.bold && a.codetag === b.codetag && a.italic === b.italic && a.link === b.link && a.strikethrough === b.strikethrough && a.olist === b.olist && a.ulist === b.ulist;
|
|
1374
|
+
};
|
|
1375
|
+
const isEmptyTextBlock = $block => {
|
|
1376
|
+
const blockChildren = $block.childNodes;
|
|
1377
|
+
if (blockChildren.length > 1) {
|
|
1378
|
+
return false;
|
|
1379
|
+
}
|
|
1380
|
+
const $inline = blockChildren[0];
|
|
1381
|
+
const isEmptyText = isInline($inline) && !isFormatCodetag($inline) && isEmptyTextNode(getChildText($inline));
|
|
1382
|
+
return isEmptyText;
|
|
1383
|
+
};
|
|
1384
|
+
export const isEditorEmpty = $root => {
|
|
1385
|
+
const rootChildren = $root.childNodes;
|
|
1386
|
+
if (rootChildren.length > 1) {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
const $block = rootChildren[0];
|
|
1390
|
+
assertTextBlock($block);
|
|
1391
|
+
return isEmptyTextBlock($block);
|
|
1392
|
+
};
|
|
1393
|
+
const serializeDescriptorReducer = range => (state, $n) => {
|
|
1394
|
+
if (isEmoji($n)) {
|
|
1395
|
+
const text = $n.getAttribute(EMOJI_CHAR_ATTR_NAME) ?? '';
|
|
1396
|
+
if (text.length > 0) {
|
|
1397
|
+
state.push({
|
|
1398
|
+
isEmoji: true,
|
|
1399
|
+
text
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
return state;
|
|
1403
|
+
}
|
|
1404
|
+
let text = getTextValue(getChildText($n));
|
|
1405
|
+
let trailingSpaces = '';
|
|
1406
|
+
if (range !== null) {
|
|
1407
|
+
const [aCursor, bCursor] = range;
|
|
1408
|
+
const isACursorPointingHere = aCursor.$inline === $n;
|
|
1409
|
+
const isBCursorPointingHere = bCursor.$inline === $n;
|
|
1410
|
+
if (isACursorPointingHere && isBCursorPointingHere) {
|
|
1411
|
+
text = text.substring(aCursor.offset, bCursor.offset);
|
|
1412
|
+
} else if (isACursorPointingHere) {
|
|
1413
|
+
text = text.substring(aCursor.offset);
|
|
1414
|
+
} else if (isBCursorPointingHere) {
|
|
1415
|
+
text = text.substring(0, bCursor.offset);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
if (isEmptyText(text)) {
|
|
1419
|
+
return state;
|
|
1420
|
+
}
|
|
1421
|
+
if (isFormatCodetag($n)) {
|
|
1422
|
+
state.push({
|
|
1423
|
+
isCodetag: true,
|
|
1424
|
+
text
|
|
1425
|
+
});
|
|
1426
|
+
return state;
|
|
1427
|
+
}
|
|
1428
|
+
if (isFormatLink($n)) {
|
|
1429
|
+
const href = $n.getAttribute(LINK_HREF_ATTR_NAME) ?? '#';
|
|
1430
|
+
state.push({
|
|
1431
|
+
isLink: true,
|
|
1432
|
+
text,
|
|
1433
|
+
href
|
|
1434
|
+
});
|
|
1435
|
+
return state;
|
|
1436
|
+
}
|
|
1437
|
+
const leadingNonSpaceIndex = text.search(/\S/);
|
|
1438
|
+
if (leadingNonSpaceIndex < 0) {
|
|
1439
|
+
state.push({
|
|
1440
|
+
isWhitespace: true,
|
|
1441
|
+
text
|
|
1442
|
+
});
|
|
1443
|
+
return state;
|
|
1444
|
+
}
|
|
1445
|
+
if (leadingNonSpaceIndex > 0) {
|
|
1446
|
+
state.push({
|
|
1447
|
+
isWhitespace: true,
|
|
1448
|
+
text: text.substring(0, leadingNonSpaceIndex)
|
|
1449
|
+
});
|
|
1450
|
+
text = text.substring(leadingNonSpaceIndex);
|
|
1451
|
+
}
|
|
1452
|
+
const trailingSpaceIndex = text.search(/\s+$/);
|
|
1453
|
+
if (trailingSpaceIndex >= 0) {
|
|
1454
|
+
trailingSpaces = text.substring(trailingSpaceIndex);
|
|
1455
|
+
text = text.substring(0, trailingSpaceIndex);
|
|
1456
|
+
}
|
|
1457
|
+
state.push({
|
|
1458
|
+
isBold: isFormatBold($n),
|
|
1459
|
+
isItalic: isFormatItalic($n),
|
|
1460
|
+
isStrikethrough: isFormatStrikethrough($n),
|
|
1461
|
+
text
|
|
1462
|
+
});
|
|
1463
|
+
if (trailingSpaces.length > 0) {
|
|
1464
|
+
state.push({
|
|
1465
|
+
isWhitespace: true,
|
|
1466
|
+
text: trailingSpaces
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
return state;
|
|
1470
|
+
};
|
|
1471
|
+
const MD_STRIKE_TOKEN = '~~';
|
|
1472
|
+
const MD_EM3_STAR_TOKEN = '***';
|
|
1473
|
+
const MD_EM2_STAR_TOKEN = '**';
|
|
1474
|
+
const MD_EM1_STAR_TOKEN = '*';
|
|
1475
|
+
const MD_EM3_UNDERSCORE_TOKEN = '___';
|
|
1476
|
+
const MD_EM2_UNDERSCORE_TOKEN = '__';
|
|
1477
|
+
const MD_EM1_UNDERSCORE_TOKEN = '_';
|
|
1478
|
+
const MD_LINEBREAK_TOKEN = ' \n';
|
|
1479
|
+
const MD_CODETAG_TOKEN = '`';
|
|
1480
|
+
const MD_ULISTITEM_TOKEN = '*';
|
|
1481
|
+
const MD_OLISTITEM_TOKEN = '1.';
|
|
1482
|
+
const MD_LISTITEM_JOIN = '\n';
|
|
1483
|
+
const MD_PARAGRAPH_JOIN = '\n\n';
|
|
1484
|
+
const serializeTextReducer = (state, desc, i, descArray) => {
|
|
1485
|
+
const {
|
|
1486
|
+
chunks
|
|
1487
|
+
} = state;
|
|
1488
|
+
if (desc.isLink === true) {
|
|
1489
|
+
chunks.push(`[${desc.text}](${desc.href})`);
|
|
1490
|
+
return state;
|
|
1491
|
+
}
|
|
1492
|
+
if (desc.isEmoji === true) {
|
|
1493
|
+
chunks.push(desc.text);
|
|
1494
|
+
return state;
|
|
1495
|
+
}
|
|
1496
|
+
if (desc.isCodetag === true) {
|
|
1497
|
+
chunks.push(`${MD_CODETAG_TOKEN}${desc.text}${MD_CODETAG_TOKEN}`);
|
|
1498
|
+
return state;
|
|
1499
|
+
}
|
|
1500
|
+
if (desc.isWhitespace === true) {
|
|
1501
|
+
chunks.push(desc.text);
|
|
1502
|
+
return state;
|
|
1503
|
+
}
|
|
1504
|
+
const prev = i === 0 ? null : descArray[i - 1];
|
|
1505
|
+
const next = i >= descArray.length - 1 ? null : descArray[i + 1];
|
|
1506
|
+
const shouldUseUnderscores = (i - state.lastUnderscoreIndex) % 2 === 0 && (prev === null || prev.isBold === true || prev.isItalic === true || prev.isWhitespace === true || prev.isCodetag === true) && (next === null || next.isBold === true || next.isItalic === true || next.isWhitespace === true || next.isCodetag === true);
|
|
1507
|
+
const {
|
|
1508
|
+
isBold,
|
|
1509
|
+
isItalic,
|
|
1510
|
+
isStrikethrough
|
|
1511
|
+
} = desc;
|
|
1512
|
+
if (shouldUseUnderscores) {
|
|
1513
|
+
state.lastUnderscoreIndex = i;
|
|
1514
|
+
}
|
|
1515
|
+
if (isStrikethrough === true && (prev === null || prev.isStrikethrough !== true)) {
|
|
1516
|
+
chunks.push(MD_STRIKE_TOKEN);
|
|
1517
|
+
}
|
|
1518
|
+
if (isBold === true && isItalic === true) {
|
|
1519
|
+
chunks.push(shouldUseUnderscores ? MD_EM3_UNDERSCORE_TOKEN : MD_EM3_STAR_TOKEN);
|
|
1520
|
+
} else if (isBold === true) {
|
|
1521
|
+
chunks.push(shouldUseUnderscores ? MD_EM2_UNDERSCORE_TOKEN : MD_EM2_STAR_TOKEN);
|
|
1522
|
+
} else if (isItalic === true) {
|
|
1523
|
+
chunks.push(shouldUseUnderscores ? MD_EM1_UNDERSCORE_TOKEN : MD_EM1_STAR_TOKEN);
|
|
1524
|
+
}
|
|
1525
|
+
chunks.push(desc.text);
|
|
1526
|
+
if (isBold === true && isItalic === true) {
|
|
1527
|
+
chunks.push(shouldUseUnderscores ? MD_EM3_UNDERSCORE_TOKEN : MD_EM3_STAR_TOKEN);
|
|
1528
|
+
} else if (isBold === true) {
|
|
1529
|
+
chunks.push(shouldUseUnderscores ? MD_EM2_UNDERSCORE_TOKEN : MD_EM2_STAR_TOKEN);
|
|
1530
|
+
} else if (isItalic === true) {
|
|
1531
|
+
chunks.push(shouldUseUnderscores ? MD_EM1_UNDERSCORE_TOKEN : MD_EM1_STAR_TOKEN);
|
|
1532
|
+
}
|
|
1533
|
+
if (isStrikethrough === true && (next === null || next.isStrikethrough !== true)) {
|
|
1534
|
+
chunks.push(MD_STRIKE_TOKEN);
|
|
1535
|
+
}
|
|
1536
|
+
return state;
|
|
1537
|
+
};
|
|
1538
|
+
const serializeTextBlock = ($n, range) => {
|
|
1539
|
+
let children;
|
|
1540
|
+
if (range !== null) {
|
|
1541
|
+
const [aCursor, bCursor] = range;
|
|
1542
|
+
const aBlock = getParentTextBlock(aCursor.$inline);
|
|
1543
|
+
const bBlock = getParentTextBlock(bCursor.$inline);
|
|
1544
|
+
const startIndex = aBlock === $n ? childIndexOf($n, aCursor.$inline) : 0;
|
|
1545
|
+
const endIndex = bBlock === $n ? childIndexOf($n, bCursor.$inline) + 1 : $n.childNodes.length;
|
|
1546
|
+
children = Array.prototype.slice.call($n.childNodes, startIndex, endIndex);
|
|
1547
|
+
} else {
|
|
1548
|
+
children = Array.from($n.childNodes);
|
|
1549
|
+
}
|
|
1550
|
+
const result = children.reduce(serializeDescriptorReducer(range), []).reduce(serializeTextReducer, {
|
|
1551
|
+
lastUnderscoreIndex: 0,
|
|
1552
|
+
chunks: []
|
|
1553
|
+
});
|
|
1554
|
+
return result.chunks.join('');
|
|
1555
|
+
};
|
|
1556
|
+
const serializeListItemIndent = (level, isOrderedStack) => {
|
|
1557
|
+
let res = '';
|
|
1558
|
+
for (let i = 0; i < level; i++) {
|
|
1559
|
+
res = res.concat(isOrderedStack[i] ? ' ' : ' ', ' ');
|
|
1560
|
+
}
|
|
1561
|
+
return res;
|
|
1562
|
+
};
|
|
1563
|
+
const serializeListItem = ($li, isOrderedStack, range) => {
|
|
1564
|
+
const isOrdered = isOrderedListItem($li);
|
|
1565
|
+
const listMdToken = isOrdered ? MD_OLISTITEM_TOKEN : MD_ULISTITEM_TOKEN;
|
|
1566
|
+
const level = getListItemLevel($li);
|
|
1567
|
+
isOrderedStack[level] = isOrdered;
|
|
1568
|
+
return `${serializeListItemIndent(level, isOrderedStack)}${listMdToken} ${serializeTextBlock($li, range)}`;
|
|
1569
|
+
};
|
|
1570
|
+
const serializeRoot = ($root, range) => {
|
|
1571
|
+
let children;
|
|
1572
|
+
if (range !== null) {
|
|
1573
|
+
const [aCursor, bCursor] = range;
|
|
1574
|
+
const aBlock = getParentTextBlock(aCursor.$inline);
|
|
1575
|
+
const bBlock = getParentTextBlock(bCursor.$inline);
|
|
1576
|
+
const startIndex = childIndexOf($root, aBlock);
|
|
1577
|
+
const endIndex = childIndexOf($root, bBlock) + 1;
|
|
1578
|
+
children = Array.prototype.slice.call($root.childNodes, startIndex, endIndex);
|
|
1579
|
+
} else {
|
|
1580
|
+
children = Array.from($root.childNodes);
|
|
1581
|
+
}
|
|
1582
|
+
const chunks = [];
|
|
1583
|
+
const listChunks = [];
|
|
1584
|
+
const listIsOrderedStack = [];
|
|
1585
|
+
const paragraphChunks = [];
|
|
1586
|
+
const flushListChunks = () => {
|
|
1587
|
+
if (listChunks.length > 0) {
|
|
1588
|
+
chunks.push(listChunks.join(MD_LISTITEM_JOIN));
|
|
1589
|
+
listChunks.length = 0;
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
const flushParagraphChunks = () => {
|
|
1593
|
+
if (paragraphChunks.length > 0) {
|
|
1594
|
+
chunks.push(paragraphChunks.reduce((a, b) => {
|
|
1595
|
+
return b.length > 0 ? a.concat(MD_LINEBREAK_TOKEN, b) : a.concat('<br>');
|
|
1596
|
+
}));
|
|
1597
|
+
paragraphChunks.length = 0;
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
for (let i = 0; i < children.length; i++) {
|
|
1601
|
+
const $child = children[i];
|
|
1602
|
+
if (isListItem($child)) {
|
|
1603
|
+
flushParagraphChunks();
|
|
1604
|
+
const isMainList = i === 0 || !isListItem(children[i - 1]) || isListItemMarkedArBlock($child);
|
|
1605
|
+
if (isMainList) {
|
|
1606
|
+
flushListChunks();
|
|
1607
|
+
}
|
|
1608
|
+
listChunks.push(serializeListItem($child, listIsOrderedStack, range));
|
|
1609
|
+
} else {
|
|
1610
|
+
assertTextBlock($child);
|
|
1611
|
+
flushListChunks();
|
|
1612
|
+
paragraphChunks.push(serializeTextBlock($child, range));
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
flushListChunks();
|
|
1616
|
+
flushParagraphChunks();
|
|
1617
|
+
return chunks.join(MD_PARAGRAPH_JOIN);
|
|
1618
|
+
};
|
|
1619
|
+
export const serializeMarkdown = ($root, range) => {
|
|
1620
|
+
if (range !== null) {
|
|
1621
|
+
const aCursor = createIncomingCursorFromNodeWithOffset(range.startContainer, range.startOffset);
|
|
1622
|
+
const bCursor = createIncomingCursorFromNodeWithOffset(range.endContainer, range.endOffset);
|
|
1623
|
+
return serializeRoot($root, [aCursor, bCursor]);
|
|
1624
|
+
}
|
|
1625
|
+
return serializeRoot($root, null);
|
|
1626
|
+
};
|
|
1627
|
+
export const createParseVisitor = doc => {
|
|
1628
|
+
let emojiBaseUrl = null;
|
|
1629
|
+
return {
|
|
1630
|
+
updateEmojiBaseUrl(url) {
|
|
1631
|
+
emojiBaseUrl = url;
|
|
1632
|
+
},
|
|
1633
|
+
createVisitor() {
|
|
1634
|
+
const $root = doc.createDocumentFragment();
|
|
1635
|
+
let $currentBlock = null;
|
|
1636
|
+
const listsStack = [];
|
|
1637
|
+
let isFirstListItem = false;
|
|
1638
|
+
return {
|
|
1639
|
+
emoji(emojiChar) {
|
|
1640
|
+
const $emoji = createEmoji(emojiChar, emojiBaseUrl, doc);
|
|
1641
|
+
$currentBlock.appendChild($emoji);
|
|
1642
|
+
},
|
|
1643
|
+
codetag(text) {
|
|
1644
|
+
const $inline = createInlineWithText(text, doc);
|
|
1645
|
+
setInlineFormat($inline, 'c', true);
|
|
1646
|
+
$currentBlock.appendChild($inline);
|
|
1647
|
+
},
|
|
1648
|
+
inline(text, _ref2) {
|
|
1649
|
+
let {
|
|
1650
|
+
isBold,
|
|
1651
|
+
isItalic,
|
|
1652
|
+
isStrikethrough
|
|
1653
|
+
} = _ref2;
|
|
1654
|
+
const $inline = createInlineWithText(text, doc);
|
|
1655
|
+
setInlineFormat($inline, 'b', isBold === true);
|
|
1656
|
+
setInlineFormat($inline, 'i', isItalic === true);
|
|
1657
|
+
setInlineFormat($inline, 's', isStrikethrough === true);
|
|
1658
|
+
$currentBlock.appendChild($inline);
|
|
1659
|
+
},
|
|
1660
|
+
linebreak() {
|
|
1661
|
+
if (listsStack.length === 0) {
|
|
1662
|
+
$currentBlock = createActuallyEmptyParagraph(doc);
|
|
1663
|
+
$root.appendChild($currentBlock);
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1666
|
+
link(text, href) {
|
|
1667
|
+
const $link = createLink(text, href, doc);
|
|
1668
|
+
$currentBlock.appendChild($link);
|
|
1669
|
+
},
|
|
1670
|
+
list(isOrdered) {
|
|
1671
|
+
listsStack.push({
|
|
1672
|
+
isOrdered
|
|
1673
|
+
});
|
|
1674
|
+
isFirstListItem = true;
|
|
1675
|
+
$currentBlock = null;
|
|
1676
|
+
},
|
|
1677
|
+
endList() {
|
|
1678
|
+
listsStack.length = listsStack.length - 1;
|
|
1679
|
+
isFirstListItem = false;
|
|
1680
|
+
$currentBlock = null;
|
|
1681
|
+
},
|
|
1682
|
+
listItem() {
|
|
1683
|
+
const listLevel = listsStack.length - 1;
|
|
1684
|
+
const {
|
|
1685
|
+
isOrdered
|
|
1686
|
+
} = listsStack.at(-1);
|
|
1687
|
+
const $li = createActuallyEmptyListItem(isOrdered, listLevel, doc);
|
|
1688
|
+
if (listsStack.length === 1 && isFirstListItem) {
|
|
1689
|
+
markListItemAsBlock($li);
|
|
1690
|
+
isFirstListItem = false;
|
|
1691
|
+
}
|
|
1692
|
+
$root.appendChild($li);
|
|
1693
|
+
$currentBlock = $li;
|
|
1694
|
+
},
|
|
1695
|
+
paragraph() {
|
|
1696
|
+
if (listsStack.length === 0) {
|
|
1697
|
+
$currentBlock = createActuallyEmptyParagraph(doc);
|
|
1698
|
+
$root.appendChild($currentBlock);
|
|
1699
|
+
}
|
|
1700
|
+
},
|
|
1701
|
+
end() {
|
|
1702
|
+
const children = $root.childNodes;
|
|
1703
|
+
for (let i = 0; i < children.length; i++) {
|
|
1704
|
+
const child = children[i];
|
|
1705
|
+
assertTextBlock(child);
|
|
1706
|
+
ensureCorrectTextBlockIfEmpty(child);
|
|
1707
|
+
}
|
|
1708
|
+
return $root;
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
};
|
|
1714
|
+
export const setBrowserCaret = _ref3 => {
|
|
1715
|
+
let {
|
|
1716
|
+
startContainer,
|
|
1717
|
+
startOffset,
|
|
1718
|
+
endContainer,
|
|
1719
|
+
endOffset
|
|
1720
|
+
} = _ref3;
|
|
1721
|
+
const selection = document.getSelection();
|
|
1722
|
+
if (selection === null) {
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
selection.removeAllRanges();
|
|
1726
|
+
const range = document.createRange();
|
|
1727
|
+
range.setStart(startContainer, startOffset);
|
|
1728
|
+
range.setEnd(endContainer, endOffset);
|
|
1729
|
+
selection.addRange(range);
|
|
1730
|
+
};
|