@37signals/lexxy 0.9.15-alpha.4 → 0.9.16
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/lexical.esm.js +344 -0
- package/dist/lexxy.esm.js +59 -62
- package/package.json +1 -1
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isTextNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $getSelection, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $isRangeSelection, $getCommonAncestor, $findMatchingParent, TextNode } from 'lexical';
|
|
2
|
+
export * from 'lexical';
|
|
3
|
+
import { ListNode } from '@lexical/list';
|
|
4
|
+
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator } from '@lexical/utils';
|
|
5
|
+
import { $ensureForwardRangeSelection, $isAtNodeEnd } from '@lexical/selection';
|
|
6
|
+
|
|
7
|
+
/*** Only import from lexical packages in this file to prevent breaking npm package export chunking ***/
|
|
8
|
+
|
|
9
|
+
function $containsRangeSelection(node, selection = $getSelection()) {
|
|
10
|
+
if ($isRangeSelection(selection)) {
|
|
11
|
+
const { commonAncestor } = $getCommonAncestor(selection.focus.getNode(), selection.anchor.getNode());
|
|
12
|
+
return $findMatchingParent(commonAncestor, parent => parent.is(node))
|
|
13
|
+
} else {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function $createNodeSelectionWith(...nodes) {
|
|
19
|
+
const selection = $createNodeSelection();
|
|
20
|
+
nodes.forEach(node => selection.add(node.getKey()));
|
|
21
|
+
return selection
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function $isShadowRoot(node) {
|
|
25
|
+
return $isElementNode(node) && $isRootOrShadowRoot(node) && !$isRootNode(node)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function $isSafeForRoot(node) {
|
|
29
|
+
return ($isElementNode(node) || $isDecoratorNode(node)) && !node.isParentRequired()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function $makeSafeForRoot(node) {
|
|
33
|
+
if ($isSafeForRoot(node)) {
|
|
34
|
+
return node
|
|
35
|
+
} else {
|
|
36
|
+
return $wrapNodeInElement(node, () => node.createParentElementNode())
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getListType(node) {
|
|
41
|
+
const list = $getNearestNodeOfType(node, ListNode);
|
|
42
|
+
return list?.getListType() ?? null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isEditorFocused(editor) {
|
|
46
|
+
const rootElement = editor.getRootElement();
|
|
47
|
+
return rootElement !== null && rootElement.contains(document.activeElement)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function $isAtNodeEdge(point, atStart = null) {
|
|
51
|
+
if (atStart === null) {
|
|
52
|
+
return $isAtNodeEdge(point, true) || $isAtNodeEdge(point, false)
|
|
53
|
+
} else {
|
|
54
|
+
return atStart ? $isAtNodeStart(point) : $isAtNodeEnd(point)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function $isAtNodeStart(point) {
|
|
59
|
+
return point.offset === 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function extendTextNodeConversion(conversionName, ...callbacks) {
|
|
63
|
+
return extendConversion(TextNode, conversionName, (conversionOutput, element) => ({
|
|
64
|
+
...conversionOutput,
|
|
65
|
+
forChild: (lexicalNode, parentNode) => {
|
|
66
|
+
const originalForChild = conversionOutput?.forChild ?? (x => x);
|
|
67
|
+
let childNode = originalForChild(lexicalNode, parentNode);
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if ($isTextNode(childNode)) {
|
|
71
|
+
childNode = callbacks.reduce(
|
|
72
|
+
(childNode, callback) => callback(childNode, element) ?? childNode,
|
|
73
|
+
childNode
|
|
74
|
+
);
|
|
75
|
+
return childNode
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function extendConversion(nodeKlass, conversionName, callback = (output => output)) {
|
|
82
|
+
return (element) => {
|
|
83
|
+
const converter = nodeKlass.importDOM()?.[conversionName]?.(element);
|
|
84
|
+
if (!converter) return null
|
|
85
|
+
|
|
86
|
+
const conversionOutput = converter.conversion(element);
|
|
87
|
+
if (!conversionOutput) return conversionOutput
|
|
88
|
+
|
|
89
|
+
return callback(conversionOutput, element) ?? conversionOutput
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function $isCursorOnLastLine(selection) {
|
|
94
|
+
const anchorNode = selection.anchor.getNode();
|
|
95
|
+
const elementNode = $isElementNode(anchorNode) ? anchorNode : anchorNode.getParentOrThrow();
|
|
96
|
+
const children = elementNode.getChildren();
|
|
97
|
+
if (children.length === 0) return true
|
|
98
|
+
|
|
99
|
+
const lastChild = children[children.length - 1];
|
|
100
|
+
|
|
101
|
+
if (anchorNode === elementNode.getLatest() && selection.anchor.offset === children.length) return true
|
|
102
|
+
if (anchorNode === lastChild) return true
|
|
103
|
+
|
|
104
|
+
const lastLineBreakIndex = children.findLastIndex(child => $isLineBreakNode(child));
|
|
105
|
+
if (lastLineBreakIndex === -1) return true
|
|
106
|
+
|
|
107
|
+
const anchorIndex = children.indexOf(anchorNode);
|
|
108
|
+
return anchorIndex > lastLineBreakIndex
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function $isBlankNode(node) {
|
|
112
|
+
if (node.getTextContent().trim() !== "") return false
|
|
113
|
+
|
|
114
|
+
const children = node.getChildren?.();
|
|
115
|
+
if (!children || children.length === 0) return true
|
|
116
|
+
|
|
117
|
+
return children.every(child => {
|
|
118
|
+
if ($isLineBreakNode(child)) return true
|
|
119
|
+
return $isBlankNode(child)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function $trimTrailingBlankNodes(parent) {
|
|
124
|
+
for (const child of $lastToFirstIterator(parent)) {
|
|
125
|
+
if ($isBlankNode(child)) {
|
|
126
|
+
child.remove();
|
|
127
|
+
} else {
|
|
128
|
+
break
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// A list item is structurally empty if it contains no meaningful content.
|
|
134
|
+
// Unlike getTextContent().trim() === "", this walks descendants to ensure
|
|
135
|
+
// decorator nodes (mentions, attachments whose getTextContent() may return
|
|
136
|
+
// invisible characters like \ufeff) are treated as non-empty content.
|
|
137
|
+
function $isListItemStructurallyEmpty(listItem) {
|
|
138
|
+
const children = listItem.getChildren();
|
|
139
|
+
for (const child of children) {
|
|
140
|
+
if ($isDecoratorNode(child)) return false
|
|
141
|
+
if ($isLineBreakNode(child)) continue
|
|
142
|
+
if ($isTextNode(child)) {
|
|
143
|
+
if (child.getTextContent().trim() !== "") return false
|
|
144
|
+
} else if ($isElementNode(child)) {
|
|
145
|
+
if (child.getTextContent().trim() !== "") return false
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Returns the document text up to `offset` inside `targetNode`. Non-inline
|
|
152
|
+
// element siblings are joined with `\n\n`, matching Lexical's own
|
|
153
|
+
// ElementNode.getTextContent behavior.
|
|
154
|
+
function $textBeforeOffset(targetNode, offset) {
|
|
155
|
+
const parts = [];
|
|
156
|
+
let done = false;
|
|
157
|
+
|
|
158
|
+
function visit(node) {
|
|
159
|
+
if (done) return
|
|
160
|
+
if (node === targetNode) {
|
|
161
|
+
parts.push(node.getTextContent().slice(0, offset));
|
|
162
|
+
done = true;
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
if ($isElementNode(node)) {
|
|
166
|
+
const children = node.getChildren();
|
|
167
|
+
for (let i = 0; i < children.length; i++) {
|
|
168
|
+
visit(children[i]);
|
|
169
|
+
if (done) return
|
|
170
|
+
const child = children[i];
|
|
171
|
+
if ($isElementNode(child) && !child.isInline() && i < children.length - 1) {
|
|
172
|
+
parts.push("\n\n");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
parts.push(node.getTextContent());
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
visit($getRoot());
|
|
181
|
+
return parts.join("")
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function $splitSelectedParagraphsAtInnerLineBreaks(selection) {
|
|
185
|
+
const topLevelElements = new Set();
|
|
186
|
+
for (const node of selection.getNodes()) {
|
|
187
|
+
const topLevel = node.getTopLevelElement();
|
|
188
|
+
if (topLevel) topLevelElements.add(topLevel);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for (const element of topLevelElements) {
|
|
192
|
+
if (!$isParagraphNode(element)) continue
|
|
193
|
+
|
|
194
|
+
const children = element.getChildren();
|
|
195
|
+
if (!children.some($isLineBreakNode)) continue
|
|
196
|
+
|
|
197
|
+
const groups = [ [] ];
|
|
198
|
+
for (const child of children) {
|
|
199
|
+
if ($isLineBreakNode(child)) {
|
|
200
|
+
groups.push([]);
|
|
201
|
+
child.remove();
|
|
202
|
+
} else {
|
|
203
|
+
groups[groups.length - 1].push(child);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const group of groups) {
|
|
208
|
+
if (group.length === 0) continue
|
|
209
|
+
const paragraph = $createParagraphNode();
|
|
210
|
+
group.forEach(child => paragraph.append(child));
|
|
211
|
+
element.insertBefore(paragraph);
|
|
212
|
+
}
|
|
213
|
+
if (groups.some(group => group.length > 0)) element.remove();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function $expandSelectionToLineBreaksAndSplitAtEdges(selection) {
|
|
218
|
+
$ensureForwardRangeSelection(selection);
|
|
219
|
+
|
|
220
|
+
const focusCaret = $caretFromPoint(selection.focus, "next");
|
|
221
|
+
const anchorCaret = $caretFromPoint(selection.anchor, "previous");
|
|
222
|
+
|
|
223
|
+
// A collapsed cursor adjacent to a <br> would claim it from both sides via
|
|
224
|
+
// inward-edge; force outward-only walks so each side finds its own boundary.
|
|
225
|
+
const skipInwardEdge = selection.isCollapsed();
|
|
226
|
+
const focusBrCaret = $getCaretAtLineBreakBoundary(focusCaret, skipInwardEdge);
|
|
227
|
+
let anchorBrCaret = $getCaretAtLineBreakBoundary(anchorCaret, skipInwardEdge);
|
|
228
|
+
|
|
229
|
+
if (focusBrCaret?.origin.is(anchorBrCaret?.origin)) {
|
|
230
|
+
anchorBrCaret = null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Splitting focus first keeps the anchor <br>'s position stable.
|
|
234
|
+
const focusOuter = focusBrCaret && $splitAroundLineBreak(focusBrCaret);
|
|
235
|
+
const anchorOuter = anchorBrCaret && $splitAroundLineBreak(anchorBrCaret);
|
|
236
|
+
|
|
237
|
+
const innerStart = anchorOuter?.getNextSibling() ?? selection.anchor.getNode().getTopLevelElement();
|
|
238
|
+
const innerEnd = focusOuter?.getPreviousSibling() ?? selection.focus.getNode().getTopLevelElement();
|
|
239
|
+
if (!innerStart || !innerEnd) return
|
|
240
|
+
|
|
241
|
+
$setSelectionFromCaretRange($getCaretRange(
|
|
242
|
+
$normalizeCaret($getChildCaret(innerStart, "next")),
|
|
243
|
+
$getCaretInDirection(
|
|
244
|
+
$normalizeCaret($getChildCaret(innerEnd, "previous")),
|
|
245
|
+
"next",
|
|
246
|
+
),
|
|
247
|
+
));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function $getCaretAtLineBreakBoundary(caret, skipInwardEdge = false) {
|
|
251
|
+
const paragraph = caret.origin.getTopLevelElement();
|
|
252
|
+
if (!paragraph || !$isParagraphNode(paragraph)) return null
|
|
253
|
+
|
|
254
|
+
const lineBreak = (skipInwardEdge ? null : $inwardEdgeLineBreak(caret, paragraph))
|
|
255
|
+
?? $outwardLineBreak(caret, paragraph);
|
|
256
|
+
|
|
257
|
+
return lineBreak ? $getSiblingCaret(lineBreak, caret.direction) : null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Prefer a <br> the cursor is sitting flush against, except when a further <br>
|
|
261
|
+
// also exists outward — that one is the real paragraph break for this side.
|
|
262
|
+
function $inwardEdgeLineBreak(caret, paragraph) {
|
|
263
|
+
let candidateCaret;
|
|
264
|
+
|
|
265
|
+
if (
|
|
266
|
+
($isChildCaret(caret) && caret.origin.is(paragraph)) ||
|
|
267
|
+
($isTextPointCaret(caret) && $isExtendableTextPointCaret(caret.getFlipped()))
|
|
268
|
+
) {
|
|
269
|
+
candidateCaret = null;
|
|
270
|
+
} else if ($isSiblingCaret(caret) && caret.getParentAtCaret().is(paragraph)) {
|
|
271
|
+
candidateCaret = caret;
|
|
272
|
+
} else {
|
|
273
|
+
const childCaret = $paragraphChildCaretAtInwardEdge(caret, paragraph);
|
|
274
|
+
candidateCaret = childCaret ? $rewindSiblingCaret(childCaret) : null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (candidateCaret && $isLineBreakNode(candidateCaret.origin)) {
|
|
278
|
+
return $candidateUnlessShadowed(candidateCaret)
|
|
279
|
+
} else {
|
|
280
|
+
return null
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function $candidateUnlessShadowed(candidateCaret) {
|
|
285
|
+
const outward = candidateCaret.getNodeAtCaret();
|
|
286
|
+
return $isLineBreakNode(outward) ? null : candidateCaret.origin
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function $outwardLineBreak(caret, paragraph) {
|
|
290
|
+
const startCaret = $outwardWalkStartCaret(caret, paragraph);
|
|
291
|
+
if (!startCaret) return null
|
|
292
|
+
|
|
293
|
+
for (const { origin } of startCaret) {
|
|
294
|
+
if (!origin.getParent().is(paragraph)) break
|
|
295
|
+
if ($isLineBreakNode(origin)) return origin
|
|
296
|
+
}
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function $outwardWalkStartCaret(caret, paragraph) {
|
|
301
|
+
if (caret.getParentAtCaret().is(paragraph)) {
|
|
302
|
+
return caret
|
|
303
|
+
} else {
|
|
304
|
+
return $paragraphChildCaretContaining(caret, paragraph)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function $paragraphChildCaretContaining(caret, paragraph) {
|
|
309
|
+
let cursor = caret.getSiblingCaret();
|
|
310
|
+
while (cursor && !cursor.origin.getParent()?.is(paragraph)) {
|
|
311
|
+
cursor = cursor.getParentCaret();
|
|
312
|
+
}
|
|
313
|
+
return cursor?.origin.getParent()?.is(paragraph) ? cursor : null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Only succeeds when the cursor is flush against the inward edge of every
|
|
317
|
+
// ancestor between itself and the paragraph child.
|
|
318
|
+
function $paragraphChildCaretAtInwardEdge(caret, paragraph) {
|
|
319
|
+
let cursor = caret.getSiblingCaret();
|
|
320
|
+
while (cursor && !cursor.origin.getParent()?.is(paragraph)) {
|
|
321
|
+
if (cursor.getNodeAtCaret()) return null
|
|
322
|
+
cursor = cursor.getParentCaret();
|
|
323
|
+
}
|
|
324
|
+
return cursor?.origin.getParent()?.is(paragraph) ? cursor : null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function $splitAroundLineBreak(lineBreakCaret) {
|
|
328
|
+
let outer = null;
|
|
329
|
+
|
|
330
|
+
if (lineBreakCaret.getNodeAtCaret() === null) {
|
|
331
|
+
lineBreakCaret.origin.remove();
|
|
332
|
+
} else {
|
|
333
|
+
const lineBreak = lineBreakCaret.origin;
|
|
334
|
+
const splitCaret = $getCaretInDirection($rewindSiblingCaret(lineBreakCaret), "next");
|
|
335
|
+
|
|
336
|
+
$splitAtPointCaretNext(splitCaret);
|
|
337
|
+
outer = lineBreak.getTopLevelElement();
|
|
338
|
+
lineBreak.remove();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return outer
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export { $containsRangeSelection, $createNodeSelectionWith, $expandSelectionToLineBreaksAndSplitAtEdges, $isAtNodeEdge, $isAtNodeStart, $isBlankNode, $isCursorOnLastLine, $isListItemStructurallyEmpty, $isSafeForRoot, $isShadowRoot, $makeSafeForRoot, $splitSelectedParagraphsAtInnerLineBreaks, $textBeforeOffset, $trimTrailingBlankNodes, extendConversion, extendTextNodeConversion, getListType, isEditorFocused };
|
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { highlightCode, highlightElement } from './lexxy_helpers.esm.js';
|
|
2
2
|
import DOMPurify from 'dompurify';
|
|
3
3
|
import { getStyleObjectFromCSS, getCSSFromStyleObject, $getSelectionStyleValueForProperty, $ensureForwardRangeSelection, $isAtNodeEnd, $patchStyleText, $setBlocksType, $forEachSelectedTextNode } from '@lexical/selection';
|
|
4
|
-
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isTextNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, defineExtension, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $isNodeSelection, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $addUpdateTag, ElementNode, $splitNode, $getChildCaretAtIndex, $createLineBreakNode, PASTE_COMMAND,
|
|
4
|
+
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isTextNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, defineExtension, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $isNodeSelection, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $addUpdateTag, ElementNode, $splitNode, $getChildCaretAtIndex, $createLineBreakNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, PASTE_COMMAND, $onUpdate, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, mergeRegister as mergeRegister$1, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, INPUT_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
5
5
|
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, $isLinkNode, AutoLinkNode } from '@lexical/link';
|
|
6
6
|
import { buildEditorFromExtensions } from '@lexical/extension';
|
|
7
7
|
import { ListNode, ListItemNode, $getListDepth, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $isListItemNode, $isListNode, registerList } from '@lexical/list';
|
|
@@ -1449,7 +1449,7 @@ function filterMatchPosition(text, potentialMatch) {
|
|
|
1449
1449
|
|
|
1450
1450
|
if (!normalizedMatch) return 0
|
|
1451
1451
|
|
|
1452
|
-
const match = normalizedText.match(new RegExp(`(
|
|
1452
|
+
const match = normalizedText.match(new RegExp(`(?<![\\p{L}\\p{N}])${escapeForRegExp(normalizedMatch)}`, "u"));
|
|
1453
1453
|
return match ? match.index : -1
|
|
1454
1454
|
}
|
|
1455
1455
|
|
|
@@ -5441,14 +5441,13 @@ class Contents {
|
|
|
5441
5441
|
}
|
|
5442
5442
|
|
|
5443
5443
|
insertDOM(doc, { tag } = {}) {
|
|
5444
|
-
this.#unwrapPlaceholderAnchors(doc);
|
|
5445
|
-
|
|
5446
5444
|
this.editor.update(() => {
|
|
5447
|
-
if ($hasUpdateTag(PASTE_TAG)) this.#
|
|
5445
|
+
if ($hasUpdateTag(PASTE_TAG)) this.#formatPastedDOM(doc);
|
|
5448
5446
|
|
|
5449
5447
|
const nodes = this.editorElement.$generateNodesFromDOM(doc);
|
|
5450
|
-
|
|
5451
|
-
|
|
5448
|
+
|
|
5449
|
+
if (!$hasUpdateTag(PASTE_TAG) || !this.#dispatchPastedNodesCommand(nodes)) {
|
|
5450
|
+
this.#insertUploadNodes(nodes) || this.insertAtCursor(...nodes);
|
|
5452
5451
|
}
|
|
5453
5452
|
}, { tag });
|
|
5454
5453
|
}
|
|
@@ -5768,6 +5767,17 @@ class Contents {
|
|
|
5768
5767
|
});
|
|
5769
5768
|
}
|
|
5770
5769
|
|
|
5770
|
+
#formatPastedDOM(doc) {
|
|
5771
|
+
this.#unwrapPlaceholderAnchors(doc);
|
|
5772
|
+
this.#stripTableCellColorStyles(doc);
|
|
5773
|
+
}
|
|
5774
|
+
|
|
5775
|
+
#dispatchPastedNodesCommand(nodes) {
|
|
5776
|
+
return this.editor.dispatchCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, {
|
|
5777
|
+
nodes, selection: $getSelection()
|
|
5778
|
+
})
|
|
5779
|
+
}
|
|
5780
|
+
|
|
5771
5781
|
#insertNodeIfRoot(node) {
|
|
5772
5782
|
const selection = $getSelection();
|
|
5773
5783
|
if (!$isRangeSelection(selection)) return false
|
|
@@ -6062,10 +6072,10 @@ class Clipboard {
|
|
|
6062
6072
|
|
|
6063
6073
|
#handleParsedClipboardNodes({ nodes, selection }) {
|
|
6064
6074
|
const url = $bareUrlFromSingleLink(nodes);
|
|
6065
|
-
if (
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6075
|
+
if (url && $isRangeSelection(selection)) {
|
|
6076
|
+
this.#insertSingleLinkAt(selection, url);
|
|
6077
|
+
return true
|
|
6078
|
+
}
|
|
6069
6079
|
}
|
|
6070
6080
|
|
|
6071
6081
|
#isPlainTextOrURLPasted(clipboardData) {
|
|
@@ -6146,10 +6156,7 @@ class Clipboard {
|
|
|
6146
6156
|
const linkNode = $createLinkNode(url).append($createTextNode(url));
|
|
6147
6157
|
selection.insertNodes([ linkNode ]);
|
|
6148
6158
|
|
|
6149
|
-
|
|
6150
|
-
// listeners may run editor mutations of their own.
|
|
6151
|
-
const nodeKey = linkNode.getKey();
|
|
6152
|
-
Promise.resolve().then(() => this.#dispatchLinkInsertEvent(nodeKey, { url }));
|
|
6159
|
+
$onUpdate(() => this.#dispatchLinkInsertEvent(linkNode.getKey(), { url }));
|
|
6153
6160
|
}
|
|
6154
6161
|
|
|
6155
6162
|
#dispatchLinkInsertEvent(nodeKey, payload) {
|
|
@@ -7605,9 +7612,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7605
7612
|
static observedAttributes = [ "connected", "required" ]
|
|
7606
7613
|
|
|
7607
7614
|
#initialValue = ""
|
|
7615
|
+
#previousInternalFormValue = null
|
|
7616
|
+
|
|
7608
7617
|
#initializeEventDispatched = false
|
|
7609
7618
|
#editorInitializedDispatched = false
|
|
7610
|
-
#valueLoaded = false
|
|
7611
7619
|
#listeners = new ListenerBin()
|
|
7612
7620
|
#disposables = []
|
|
7613
7621
|
#historyState = { undo: false, redo: false }
|
|
@@ -7659,12 +7667,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7659
7667
|
disconnectedCallback() {
|
|
7660
7668
|
this.#initializeEventDispatched = false;
|
|
7661
7669
|
this.#editorInitializedDispatched = false;
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
this.#valueLoaded = false;
|
|
7670
|
+
|
|
7671
|
+
this.#previousInternalFormValue = null;
|
|
7672
|
+
this.valueBeforeDisconnect = this.value;
|
|
7673
|
+
|
|
7674
|
+
this.#clearCachedValues();
|
|
7668
7675
|
this.#reset(); // Prevent hangs with Safari when morphing
|
|
7669
7676
|
}
|
|
7670
7677
|
|
|
@@ -7689,13 +7696,9 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7689
7696
|
}
|
|
7690
7697
|
|
|
7691
7698
|
toString() {
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
});
|
|
7696
|
-
}
|
|
7697
|
-
|
|
7698
|
-
return this.cachedStringValue
|
|
7699
|
+
return this.cachedStringValue ??= this.editor?.read(() => {
|
|
7700
|
+
return $getReadableTextContent($getRoot())
|
|
7701
|
+
})
|
|
7699
7702
|
}
|
|
7700
7703
|
|
|
7701
7704
|
get form() {
|
|
@@ -7779,8 +7782,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7779
7782
|
return dispatch(this, "lexxy:file-accept", { file }, true)
|
|
7780
7783
|
}
|
|
7781
7784
|
|
|
7782
|
-
$generateNodesFromDOM(doc) {
|
|
7783
|
-
let nodes = $generateNodesFromDOM(
|
|
7785
|
+
$generateNodesFromDOM(doc, { editor = this.editor } = {}) {
|
|
7786
|
+
let nodes = $generateNodesFromDOM(editor, doc);
|
|
7784
7787
|
if ($hasUpdateTag(PASTE_TAG)) nodes = $convertInlineImageDataURIs(nodes, this);
|
|
7785
7788
|
return filterDisallowedAttachmentNodes(nodes, this)
|
|
7786
7789
|
}
|
|
@@ -7822,7 +7825,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7822
7825
|
|
|
7823
7826
|
if (!this.editor) return
|
|
7824
7827
|
|
|
7825
|
-
this.#editorInitializedDispatched = true;
|
|
7826
7828
|
this.#dispatchEditorInitialized();
|
|
7827
7829
|
this.#dispatchAttributesChange();
|
|
7828
7830
|
}
|
|
@@ -7867,17 +7869,12 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7867
7869
|
}
|
|
7868
7870
|
|
|
7869
7871
|
get value() {
|
|
7870
|
-
|
|
7871
|
-
this.editor
|
|
7872
|
-
|
|
7873
|
-
});
|
|
7874
|
-
}
|
|
7875
|
-
|
|
7876
|
-
return this.cachedValue
|
|
7872
|
+
return this.cachedValue ??= this.editor?.read(() => {
|
|
7873
|
+
return sanitize($generateHtmlFromNodes(this.editor, null))
|
|
7874
|
+
}) ?? null
|
|
7877
7875
|
}
|
|
7878
7876
|
|
|
7879
7877
|
set value(html) {
|
|
7880
|
-
this.#valueLoaded = true;
|
|
7881
7878
|
const editorHasFocus = this.#isContentFocused;
|
|
7882
7879
|
|
|
7883
7880
|
this.editor.update(() => {
|
|
@@ -7889,11 +7886,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7889
7886
|
$addUpdateTag(SKIP_DOM_SELECTION_TAG);
|
|
7890
7887
|
}
|
|
7891
7888
|
|
|
7892
|
-
$getRoot()
|
|
7893
|
-
.clear()
|
|
7894
|
-
.selectEnd()
|
|
7895
|
-
.insertNodes(this.#parseHtmlIntoLexicalNodes(html));
|
|
7896
7889
|
|
|
7890
|
+
this.#setEditorHtml(html);
|
|
7897
7891
|
this.#toggleEmptyStatus();
|
|
7898
7892
|
}, { discrete: true });
|
|
7899
7893
|
}
|
|
@@ -7906,9 +7900,9 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7906
7900
|
return this.#historyState.redo
|
|
7907
7901
|
}
|
|
7908
7902
|
|
|
7909
|
-
#parseHtmlIntoLexicalNodes(html) {
|
|
7903
|
+
#parseHtmlIntoLexicalNodes(html, { editor = this.editor } = {}) {
|
|
7910
7904
|
if (!html) html = "<p></p>";
|
|
7911
|
-
const nodes = this.$generateNodesFromDOM(parseHtml(`${html}`));
|
|
7905
|
+
const nodes = this.$generateNodesFromDOM(parseHtml(`${html}`), { editor });
|
|
7912
7906
|
|
|
7913
7907
|
return nodes
|
|
7914
7908
|
.filter(this.#isNotWhitespaceOnlyNode)
|
|
@@ -7944,7 +7938,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7944
7938
|
this.#attachDebugHooks();
|
|
7945
7939
|
this.#attachToolbar();
|
|
7946
7940
|
this.#configureSanitizer();
|
|
7947
|
-
this.#loadInitialValue();
|
|
7948
7941
|
this.#resetBeforeTurboCaches();
|
|
7949
7942
|
}
|
|
7950
7943
|
|
|
@@ -7969,7 +7962,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7969
7962
|
nodes: this.#lexicalNodes,
|
|
7970
7963
|
html: {
|
|
7971
7964
|
export: new Map([ [ TextNode, exportTextNodeDOM ], [ CodeHighlightNode, exportTextNodeDOM ] ])
|
|
7972
|
-
}
|
|
7965
|
+
},
|
|
7966
|
+
$initialEditorState: (editor) => this.#loadInitialValue(editor)
|
|
7973
7967
|
},
|
|
7974
7968
|
...this.extensions.lexicalExtensions
|
|
7975
7969
|
);
|
|
@@ -8036,28 +8030,30 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
8036
8030
|
return Array.from(this.attributes).filter(attribute => attribute.name.startsWith("aria-"))
|
|
8037
8031
|
}
|
|
8038
8032
|
|
|
8039
|
-
|
|
8040
|
-
const changed = this.#
|
|
8033
|
+
#setInternalFormValue(html) {
|
|
8034
|
+
const changed = this.#previousInternalFormValue !== null && html !== this.#previousInternalFormValue;
|
|
8041
8035
|
|
|
8042
8036
|
this.internals.setFormValue(html);
|
|
8043
|
-
this
|
|
8037
|
+
this.#previousInternalFormValue = html;
|
|
8044
8038
|
|
|
8045
8039
|
if (changed) {
|
|
8046
8040
|
dispatch(this, "lexxy:change");
|
|
8047
8041
|
}
|
|
8048
8042
|
}
|
|
8049
8043
|
|
|
8050
|
-
|
|
8051
|
-
|
|
8044
|
+
#loadInitialValue(editor) {
|
|
8045
|
+
const initialHtml = this.valueBeforeDisconnect || this.getAttribute("value") || "<p><br></p>";
|
|
8046
|
+
|
|
8047
|
+
this.#initialValue = initialHtml;
|
|
8048
|
+
this.#setInternalFormValue(initialHtml);
|
|
8049
|
+
this.#setEditorHtml(initialHtml, { editor });
|
|
8052
8050
|
}
|
|
8053
8051
|
|
|
8054
|
-
#
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
}, { tag: HISTORY_MERGE_TAG });
|
|
8060
|
-
}
|
|
8052
|
+
#setEditorHtml(html, { editor = this.editor } = { }) {
|
|
8053
|
+
$getRoot()
|
|
8054
|
+
.clear()
|
|
8055
|
+
.selectEnd()
|
|
8056
|
+
.insertNodes(this.#parseHtmlIntoLexicalNodes(html, { editor }));
|
|
8061
8057
|
}
|
|
8062
8058
|
|
|
8063
8059
|
#resetBeforeTurboCaches() {
|
|
@@ -8075,7 +8071,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
8075
8071
|
#synchronizeWithChanges() {
|
|
8076
8072
|
this.#listeners.track(this.editor.registerUpdateListener(({ editorState }) => {
|
|
8077
8073
|
this.#clearCachedValues();
|
|
8078
|
-
this.#
|
|
8074
|
+
this.#setInternalFormValue(this.value);
|
|
8079
8075
|
this.#toggleEmptyStatus();
|
|
8080
8076
|
this.#requestValidityRefresh();
|
|
8081
8077
|
this.#dispatchAttributesChange();
|
|
@@ -8333,6 +8329,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
8333
8329
|
#dispatchEditorInitialized() {
|
|
8334
8330
|
if (!this.adapter) return
|
|
8335
8331
|
|
|
8332
|
+
this.#editorInitializedDispatched = true;
|
|
8333
|
+
|
|
8336
8334
|
this.adapter.dispatchEditorInitialized({
|
|
8337
8335
|
highlightColors: this.#resolvedHighlightColors,
|
|
8338
8336
|
headingFormats: this.#supportedHeadingFormats
|
|
@@ -8347,7 +8345,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
8347
8345
|
}
|
|
8348
8346
|
|
|
8349
8347
|
if (!this.#editorInitializedDispatched) {
|
|
8350
|
-
this.#editorInitializedDispatched = true;
|
|
8351
8348
|
this.#dispatchEditorInitialized();
|
|
8352
8349
|
}
|
|
8353
8350
|
}
|