@lexical/react 0.1.7 → 0.1.8
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/DEPRECATED_useLexical.dev.js +84 -0
- package/DEPRECATED_useLexical.js +9 -2
- package/DEPRECATED_useLexical.prod.js +8 -0
- package/DEPRECATED_useLexicalAutoFormatter.dev.js +642 -0
- package/DEPRECATED_useLexicalAutoFormatter.js +9 -11
- package/DEPRECATED_useLexicalAutoFormatter.prod.js +22 -0
- package/DEPRECATED_useLexicalCanShowPlaceholder.dev.js +136 -0
- package/DEPRECATED_useLexicalCanShowPlaceholder.js +9 -3
- package/DEPRECATED_useLexicalCanShowPlaceholder.prod.js +8 -0
- package/DEPRECATED_useLexicalCharacterLimit.dev.js +294 -0
- package/DEPRECATED_useLexicalCharacterLimit.js +9 -8
- package/DEPRECATED_useLexicalCharacterLimit.prod.js +14 -0
- package/DEPRECATED_useLexicalDecorators.dev.js +82 -0
- package/DEPRECATED_useLexicalDecorators.js +9 -2
- package/DEPRECATED_useLexicalDecorators.prod.js +8 -0
- package/DEPRECATED_useLexicalEditor.dev.js +52 -0
- package/DEPRECATED_useLexicalEditor.js +9 -1
- package/DEPRECATED_useLexicalEditor.prod.js +7 -0
- package/DEPRECATED_useLexicalEditorEvents.dev.js +96 -0
- package/DEPRECATED_useLexicalEditorEvents.js +9 -2
- package/DEPRECATED_useLexicalEditorEvents.prod.js +8 -0
- package/DEPRECATED_useLexicalHistory.dev.js +339 -0
- package/DEPRECATED_useLexicalHistory.js +9 -7
- package/DEPRECATED_useLexicalHistory.prod.js +13 -0
- package/DEPRECATED_useLexicalList.dev.js +64 -0
- package/DEPRECATED_useLexicalList.js +9 -1
- package/DEPRECATED_useLexicalList.prod.js +7 -0
- package/DEPRECATED_useLexicalPlainText.dev.js +755 -0
- package/DEPRECATED_useLexicalPlainText.js +9 -16
- package/DEPRECATED_useLexicalPlainText.prod.js +22 -0
- package/DEPRECATED_useLexicalRichText.dev.js +1326 -0
- package/DEPRECATED_useLexicalRichText.js +9 -30
- package/DEPRECATED_useLexicalRichText.prod.js +35 -0
- package/LexicalAutoFormatterPlugin.dev.js +645 -0
- package/LexicalAutoFormatterPlugin.js +9 -12
- package/LexicalAutoFormatterPlugin.prod.js +23 -0
- package/LexicalAutoLinkPlugin.dev.js +227 -0
- package/LexicalAutoLinkPlugin.js +9 -6
- package/LexicalAutoLinkPlugin.prod.js +12 -0
- package/LexicalBootstrapPlugin.dev.js +124 -0
- package/LexicalBootstrapPlugin.js +9 -0
- package/LexicalBootstrapPlugin.prod.js +8 -0
- package/LexicalCharacterLimitPlugin.dev.js +352 -0
- package/LexicalCharacterLimitPlugin.js +9 -8
- package/LexicalCharacterLimitPlugin.prod.js +14 -0
- package/LexicalCollaborationPlugin.dev.js +235 -0
- package/LexicalCollaborationPlugin.js +9 -8
- package/LexicalCollaborationPlugin.prod.js +14 -0
- package/LexicalComposer.dev.js +76 -0
- package/LexicalComposer.js +9 -3
- package/LexicalComposer.prod.js +9 -0
- package/LexicalComposerContext.dev.js +53 -0
- package/LexicalComposerContext.js +9 -1
- package/LexicalComposerContext.prod.js +7 -0
- package/LexicalContentEditable.dev.js +71 -0
- package/LexicalContentEditable.js +9 -3
- package/LexicalContentEditable.prod.js +9 -0
- package/LexicalHashtagPlugin.dev.js +152 -0
- package/LexicalHashtagPlugin.js +9 -4
- package/LexicalHashtagPlugin.prod.js +10 -0
- package/LexicalHistoryPlugin.dev.js +344 -0
- package/LexicalHistoryPlugin.js +9 -7
- package/LexicalHistoryPlugin.prod.js +13 -0
- package/LexicalHorizontalRulePlugin.dev.js +51 -0
- package/LexicalHorizontalRulePlugin.js +9 -1
- package/LexicalHorizontalRulePlugin.prod.js +7 -0
- package/LexicalLinkPlugin.dev.js +137 -0
- package/LexicalLinkPlugin.js +9 -3
- package/LexicalLinkPlugin.prod.js +9 -0
- package/LexicalListPlugin.dev.js +67 -0
- package/LexicalListPlugin.js +9 -2
- package/LexicalListPlugin.prod.js +8 -0
- package/LexicalNestedComposer.dev.js +60 -0
- package/LexicalNestedComposer.js +9 -2
- package/LexicalNestedComposer.prod.js +8 -0
- package/LexicalOnChangePlugin.dev.js +57 -0
- package/LexicalOnChangePlugin.js +9 -1
- package/LexicalOnChangePlugin.prod.js +7 -0
- package/LexicalPlainTextPlugin.dev.js +565 -0
- package/LexicalPlainTextPlugin.js +9 -13
- package/LexicalPlainTextPlugin.prod.js +17 -0
- package/LexicalRichTextPlugin.dev.js +1136 -0
- package/LexicalRichTextPlugin.js +9 -27
- package/LexicalRichTextPlugin.prod.js +31 -0
- package/LexicalTablePlugin.dev.js +95 -0
- package/LexicalTablePlugin.js +9 -3
- package/LexicalTablePlugin.prod.js +9 -0
- package/LexicalTreeView.dev.js +340 -0
- package/LexicalTreeView.js +9 -13
- package/LexicalTreeView.prod.js +19 -0
- package/README.md +1 -0
- package/package.json +4 -4
- package/useLexicalDecoratorMap.dev.js +83 -0
- package/useLexicalDecoratorMap.js +9 -2
- package/useLexicalDecoratorMap.prod.js +8 -0
- package/useLexicalIsTextContentEmpty.dev.js +84 -0
- package/useLexicalIsTextContentEmpty.js +9 -2
- package/useLexicalIsTextContentEmpty.prod.js +8 -0
- package/withSubscriptions.dev.js +23 -0
- package/withSubscriptions.js +9 -1
- package/withSubscriptions.prod.js +7 -0
|
@@ -0,0 +1,1326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var withSubscriptions = require('@lexical/react/withSubscriptions');
|
|
10
|
+
var lexical = require('lexical');
|
|
11
|
+
var react = require('react');
|
|
12
|
+
var list = require('@lexical/list');
|
|
13
|
+
var CodeNode = require('lexical/CodeNode');
|
|
14
|
+
var HeadingNode = require('lexical/HeadingNode');
|
|
15
|
+
var LinkNode = require('lexical/LinkNode');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
19
|
+
*
|
|
20
|
+
* This source code is licensed under the MIT license found in the
|
|
21
|
+
* LICENSE file in the root directory of this source tree.
|
|
22
|
+
*
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
const MERGE = 0;
|
|
26
|
+
const NO_MERGE = 1;
|
|
27
|
+
const DISCARD = 2;
|
|
28
|
+
const OTHER = 0;
|
|
29
|
+
const COMPOSING_CHARACTER = 1;
|
|
30
|
+
const INSERT_CHARACTER_AFTER_SELECTION = 2;
|
|
31
|
+
const DELETE_CHARACTER_BEFORE_SELECTION = 3;
|
|
32
|
+
const DELETE_CHARACTER_AFTER_SELECTION = 4;
|
|
33
|
+
const EditorPriority$1 = 0;
|
|
34
|
+
|
|
35
|
+
function getDirtyNodes(editorState, dirtyLeavesSet, dirtyElementsSet) {
|
|
36
|
+
const dirtyLeaves = Array.from(dirtyLeavesSet);
|
|
37
|
+
const dirtyElements = Array.from(dirtyElementsSet);
|
|
38
|
+
const nodeMap = editorState._nodeMap;
|
|
39
|
+
const nodes = [];
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < dirtyLeaves.length; i++) {
|
|
42
|
+
const dirtyLeafKey = dirtyLeaves[i];
|
|
43
|
+
const dirtyLeaf = nodeMap.get(dirtyLeafKey);
|
|
44
|
+
|
|
45
|
+
if (dirtyLeaf !== undefined) {
|
|
46
|
+
nodes.push(dirtyLeaf);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < dirtyElements.length; i++) {
|
|
51
|
+
const intentionallyMarkedAsDirty = dirtyElements[i][1];
|
|
52
|
+
|
|
53
|
+
if (!intentionallyMarkedAsDirty) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const dirtyElementKey = dirtyElements[i][0];
|
|
58
|
+
const dirtyElement = nodeMap.get(dirtyElementKey);
|
|
59
|
+
|
|
60
|
+
if (dirtyElement !== undefined && !lexical.$isRootNode(dirtyElement)) {
|
|
61
|
+
nodes.push(dirtyElement);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return nodes;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getChangeType(prevEditorState, nextEditorState, dirtyLeavesSet, dirtyElementsSet, isComposing) {
|
|
69
|
+
if (prevEditorState === null || dirtyLeavesSet.size === 0 && dirtyElementsSet.size === 0) {
|
|
70
|
+
return OTHER;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const nextSelection = nextEditorState._selection;
|
|
74
|
+
const prevSelection = prevEditorState._selection;
|
|
75
|
+
|
|
76
|
+
if (isComposing) {
|
|
77
|
+
return COMPOSING_CHARACTER;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (nextSelection === null || prevSelection === null || !prevSelection.isCollapsed() || !nextSelection.isCollapsed()) {
|
|
81
|
+
return OTHER;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const dirtyNodes = getDirtyNodes(nextEditorState, dirtyLeavesSet, dirtyElementsSet);
|
|
85
|
+
|
|
86
|
+
if (dirtyNodes.length === 0) {
|
|
87
|
+
return OTHER;
|
|
88
|
+
} // Catching the case when inserting new text node into an element (e.g. first char in paragraph/list),
|
|
89
|
+
// or after existing node.
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if (dirtyNodes.length > 1) {
|
|
93
|
+
const nextNodeMap = nextEditorState._nodeMap;
|
|
94
|
+
const nextAnchorNode = nextNodeMap.get(nextSelection.anchor.key);
|
|
95
|
+
const prevAnchorNode = nextNodeMap.get(prevSelection.anchor.key);
|
|
96
|
+
|
|
97
|
+
if (nextAnchorNode && prevAnchorNode && !prevEditorState._nodeMap.has(nextAnchorNode.__key) && lexical.$isTextNode(nextAnchorNode) && nextAnchorNode.__text.length === 1 && nextSelection.anchor.offset === 1) {
|
|
98
|
+
return INSERT_CHARACTER_AFTER_SELECTION;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return OTHER;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const nextDirtyNode = dirtyNodes[0];
|
|
105
|
+
|
|
106
|
+
const prevDirtyNode = prevEditorState._nodeMap.get(nextDirtyNode.__key);
|
|
107
|
+
|
|
108
|
+
if (!lexical.$isTextNode(prevDirtyNode) || !lexical.$isTextNode(nextDirtyNode) || prevDirtyNode.__mode !== nextDirtyNode.__mode) {
|
|
109
|
+
return OTHER;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const prevText = prevDirtyNode.__text;
|
|
113
|
+
const nextText = nextDirtyNode.__text;
|
|
114
|
+
|
|
115
|
+
if (prevText === nextText) {
|
|
116
|
+
return OTHER;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const nextAnchor = nextSelection.anchor;
|
|
120
|
+
const prevAnchor = prevSelection.anchor;
|
|
121
|
+
|
|
122
|
+
if (nextAnchor.key !== prevAnchor.key || nextAnchor.type !== 'text') {
|
|
123
|
+
return OTHER;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const nextAnchorOffset = nextAnchor.offset;
|
|
127
|
+
const prevAnchorOffset = prevAnchor.offset;
|
|
128
|
+
const textDiff = nextText.length - prevText.length;
|
|
129
|
+
|
|
130
|
+
if (textDiff === 1 && prevAnchorOffset === nextAnchorOffset - 1) {
|
|
131
|
+
return INSERT_CHARACTER_AFTER_SELECTION;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (textDiff === -1 && prevAnchorOffset === nextAnchorOffset + 1) {
|
|
135
|
+
return DELETE_CHARACTER_BEFORE_SELECTION;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (textDiff === -1 && prevAnchorOffset === nextAnchorOffset) {
|
|
139
|
+
return DELETE_CHARACTER_AFTER_SELECTION;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return OTHER;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function createMergeActionGetter(editor, delay) {
|
|
146
|
+
let prevChangeTime = Date.now();
|
|
147
|
+
let prevChangeType = OTHER;
|
|
148
|
+
return (prevEditorState, nextEditorState, currentHistoryEntry, dirtyLeaves, dirtyElements, tags) => {
|
|
149
|
+
const changeTime = Date.now(); // If applying changes from history stack there's no need
|
|
150
|
+
// to run history logic again, as history entries already calculated
|
|
151
|
+
|
|
152
|
+
if (tags.has('historic')) {
|
|
153
|
+
prevChangeType = OTHER;
|
|
154
|
+
prevChangeTime = changeTime;
|
|
155
|
+
return DISCARD;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const changeType = getChangeType(prevEditorState, nextEditorState, dirtyLeaves, dirtyElements, editor.isComposing());
|
|
159
|
+
|
|
160
|
+
const mergeAction = (() => {
|
|
161
|
+
if (tags.has('without-history')) {
|
|
162
|
+
return MERGE;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (prevEditorState === null) {
|
|
166
|
+
return NO_MERGE;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const selection = nextEditorState._selection;
|
|
170
|
+
const prevSelection = prevEditorState._selection;
|
|
171
|
+
const hasDirtyNodes = dirtyLeaves.size > 0 || dirtyElements.size > 0;
|
|
172
|
+
|
|
173
|
+
if (!hasDirtyNodes) {
|
|
174
|
+
if (prevSelection === null && selection !== null) {
|
|
175
|
+
return MERGE;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return DISCARD;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const isSameEditor = currentHistoryEntry === null || currentHistoryEntry.editor === editor;
|
|
182
|
+
|
|
183
|
+
if (changeType !== OTHER && changeType === prevChangeType && changeTime < prevChangeTime + delay && isSameEditor) {
|
|
184
|
+
return MERGE;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return NO_MERGE;
|
|
188
|
+
})();
|
|
189
|
+
|
|
190
|
+
prevChangeTime = changeTime;
|
|
191
|
+
prevChangeType = changeType;
|
|
192
|
+
return mergeAction;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function useHistory(editor, externalHistoryState, delay = 1000) {
|
|
197
|
+
const historyState = react.useMemo(() => externalHistoryState || createEmptyHistoryState(), [externalHistoryState]);
|
|
198
|
+
const clearHistory = react.useCallback(() => {
|
|
199
|
+
historyState.undoStack = [];
|
|
200
|
+
historyState.redoStack = [];
|
|
201
|
+
historyState.current = null;
|
|
202
|
+
}, [historyState]);
|
|
203
|
+
react.useEffect(() => {
|
|
204
|
+
const getMergeAction = createMergeActionGetter(editor, delay);
|
|
205
|
+
|
|
206
|
+
const applyChange = ({
|
|
207
|
+
editorState,
|
|
208
|
+
prevEditorState,
|
|
209
|
+
dirtyLeaves,
|
|
210
|
+
dirtyElements,
|
|
211
|
+
tags
|
|
212
|
+
}) => {
|
|
213
|
+
const current = historyState.current;
|
|
214
|
+
const redoStack = historyState.redoStack;
|
|
215
|
+
const undoStack = historyState.undoStack;
|
|
216
|
+
const currentEditorState = current === null ? null : current.editorState;
|
|
217
|
+
|
|
218
|
+
if (current !== null && editorState === currentEditorState) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const mergeAction = getMergeAction(prevEditorState, editorState, current, dirtyLeaves, dirtyElements, tags);
|
|
223
|
+
|
|
224
|
+
if (mergeAction === NO_MERGE) {
|
|
225
|
+
if (redoStack.length !== 0) {
|
|
226
|
+
historyState.redoStack = [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (current !== null) {
|
|
230
|
+
undoStack.push({ ...current,
|
|
231
|
+
undoSelection: prevEditorState.read(lexical.$getSelection)
|
|
232
|
+
});
|
|
233
|
+
editor.execCommand('canUndo', true);
|
|
234
|
+
}
|
|
235
|
+
} else if (mergeAction === DISCARD) {
|
|
236
|
+
return;
|
|
237
|
+
} // Else we merge
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
historyState.current = {
|
|
241
|
+
editor,
|
|
242
|
+
editorState
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const undo = () => {
|
|
247
|
+
const redoStack = historyState.redoStack;
|
|
248
|
+
const undoStack = historyState.undoStack;
|
|
249
|
+
const undoStackLength = undoStack.length;
|
|
250
|
+
|
|
251
|
+
if (undoStackLength !== 0) {
|
|
252
|
+
const current = historyState.current;
|
|
253
|
+
const historyStateEntry = undoStack.pop();
|
|
254
|
+
|
|
255
|
+
if (current !== null) {
|
|
256
|
+
redoStack.push(current);
|
|
257
|
+
editor.execCommand('canRedo', true);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (undoStack.length === 0) {
|
|
261
|
+
editor.execCommand('canUndo', false);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
historyState.current = historyStateEntry;
|
|
265
|
+
historyStateEntry.editor.setEditorState(historyStateEntry.editorState.clone(historyStateEntry.undoSelection), {
|
|
266
|
+
tag: 'historic'
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const redo = () => {
|
|
272
|
+
const redoStack = historyState.redoStack;
|
|
273
|
+
const undoStack = historyState.undoStack;
|
|
274
|
+
|
|
275
|
+
if (redoStack.length !== 0) {
|
|
276
|
+
const current = historyState.current;
|
|
277
|
+
|
|
278
|
+
if (current !== null) {
|
|
279
|
+
undoStack.push(current);
|
|
280
|
+
editor.execCommand('canUndo', true);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const historyStateEntry = redoStack.pop();
|
|
284
|
+
|
|
285
|
+
if (redoStack.length === 0) {
|
|
286
|
+
editor.execCommand('canRedo', false);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
historyState.current = historyStateEntry;
|
|
290
|
+
historyStateEntry.editor.setEditorState(historyStateEntry.editorState, {
|
|
291
|
+
tag: 'historic'
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const applyCommand = type => {
|
|
297
|
+
switch (type) {
|
|
298
|
+
case 'undo':
|
|
299
|
+
undo();
|
|
300
|
+
return true;
|
|
301
|
+
|
|
302
|
+
case 'redo':
|
|
303
|
+
redo();
|
|
304
|
+
return true;
|
|
305
|
+
|
|
306
|
+
case 'clearEditor':
|
|
307
|
+
clearHistory();
|
|
308
|
+
return false;
|
|
309
|
+
|
|
310
|
+
case 'clearHistory':
|
|
311
|
+
clearHistory();
|
|
312
|
+
return true;
|
|
313
|
+
|
|
314
|
+
default:
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return withSubscriptions(editor.addListener('command', applyCommand, EditorPriority$1), editor.addListener('update', applyChange));
|
|
320
|
+
}, [clearHistory, delay, editor, historyState]);
|
|
321
|
+
}
|
|
322
|
+
function createEmptyHistoryState() {
|
|
323
|
+
return {
|
|
324
|
+
current: null,
|
|
325
|
+
redoStack: [],
|
|
326
|
+
undoStack: []
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
332
|
+
*
|
|
333
|
+
* This source code is licensed under the MIT license found in the
|
|
334
|
+
* LICENSE file in the root directory of this source tree.
|
|
335
|
+
*
|
|
336
|
+
*
|
|
337
|
+
*/
|
|
338
|
+
function useLexicalHistory(editor, externalHistoryState, delay = 1000) {
|
|
339
|
+
return useHistory(editor, externalHistoryState, delay);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
344
|
+
*
|
|
345
|
+
* This source code is licensed under the MIT license found in the
|
|
346
|
+
* LICENSE file in the root directory of this source tree.
|
|
347
|
+
*
|
|
348
|
+
*
|
|
349
|
+
*/
|
|
350
|
+
|
|
351
|
+
function $cloneWithProperties(node) {
|
|
352
|
+
const latest = node.getLatest();
|
|
353
|
+
const constructor = latest.constructor;
|
|
354
|
+
const clone = constructor.clone(latest);
|
|
355
|
+
clone.__parent = latest.__parent;
|
|
356
|
+
|
|
357
|
+
if (lexical.$isElementNode(latest) && lexical.$isElementNode(clone)) {
|
|
358
|
+
clone.__children = Array.from(latest.__children);
|
|
359
|
+
clone.__format = latest.__format;
|
|
360
|
+
clone.__indent = latest.__indent;
|
|
361
|
+
clone.__dir = latest.__dir;
|
|
362
|
+
} else if (lexical.$isTextNode(latest) && lexical.$isTextNode(clone)) {
|
|
363
|
+
clone.__format = latest.__format;
|
|
364
|
+
clone.__style = latest.__style;
|
|
365
|
+
clone.__mode = latest.__mode;
|
|
366
|
+
clone.__detail = latest.__detail;
|
|
367
|
+
} else if (lexical.$isDecoratorNode(latest) && lexical.$isDecoratorNode(clone)) {
|
|
368
|
+
clone.__state = latest.__state;
|
|
369
|
+
} // $FlowFixMe
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
return clone;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function $getIndexFromPossibleClone(node, parent, nodeMap) {
|
|
376
|
+
const parentClone = nodeMap.get(parent.getKey());
|
|
377
|
+
|
|
378
|
+
if (lexical.$isElementNode(parentClone)) {
|
|
379
|
+
return parentClone.__children.indexOf(node.getKey());
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return node.getIndexWithinParent();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function $getParentAvoidingExcludedElements(node) {
|
|
386
|
+
let parent = node.getParent();
|
|
387
|
+
|
|
388
|
+
while (parent !== null && parent.excludeFromCopy()) {
|
|
389
|
+
parent = parent.getParent();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return parent;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function $copyLeafNodeBranchToRoot(leaf, startingOffset, isLeftSide, range, nodeMap) {
|
|
396
|
+
let node = leaf;
|
|
397
|
+
let offset = startingOffset;
|
|
398
|
+
|
|
399
|
+
while (node !== null) {
|
|
400
|
+
const parent = $getParentAvoidingExcludedElements(node);
|
|
401
|
+
|
|
402
|
+
if (parent === null) {
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!lexical.$isElementNode(node) || !node.excludeFromCopy()) {
|
|
407
|
+
const key = node.getKey();
|
|
408
|
+
let clone = nodeMap.get(key);
|
|
409
|
+
const needsClone = clone === undefined;
|
|
410
|
+
|
|
411
|
+
if (needsClone) {
|
|
412
|
+
clone = $cloneWithProperties(node);
|
|
413
|
+
nodeMap.set(key, clone);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (lexical.$isTextNode(clone) && !clone.isSegmented() && !clone.isToken()) {
|
|
417
|
+
clone.__text = clone.__text.slice(isLeftSide ? offset : 0, isLeftSide ? undefined : offset);
|
|
418
|
+
} else if (lexical.$isElementNode(clone)) {
|
|
419
|
+
clone.__children = clone.__children.slice(isLeftSide ? offset : 0, isLeftSide ? undefined : offset + 1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (lexical.$isRootNode(parent)) {
|
|
423
|
+
if (needsClone) {
|
|
424
|
+
// We only want to collect a range of top level nodes.
|
|
425
|
+
// So if the parent is the root, we know this is a top level.
|
|
426
|
+
range.push(key);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
offset = $getIndexFromPossibleClone(node, parent, nodeMap);
|
|
434
|
+
node = parent;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function $cloneContents(selection) {
|
|
439
|
+
const anchor = selection.anchor;
|
|
440
|
+
const focus = selection.focus;
|
|
441
|
+
const anchorOffset = anchor.getCharacterOffset();
|
|
442
|
+
const focusOffset = focus.getCharacterOffset();
|
|
443
|
+
const anchorNode = anchor.getNode();
|
|
444
|
+
const focusNode = focus.getNode();
|
|
445
|
+
const anchorNodeParent = anchorNode.getParentOrThrow(); // Handle a single text node extraction
|
|
446
|
+
|
|
447
|
+
if (anchorNode === focusNode && lexical.$isTextNode(anchorNode) && (anchorNodeParent.canBeEmpty() || anchorNodeParent.getChildrenSize() > 1)) {
|
|
448
|
+
const clonedFirstNode = $cloneWithProperties(anchorNode);
|
|
449
|
+
const isBefore = focusOffset > anchorOffset;
|
|
450
|
+
const startOffset = isBefore ? anchorOffset : focusOffset;
|
|
451
|
+
const endOffset = isBefore ? focusOffset : anchorOffset;
|
|
452
|
+
clonedFirstNode.__text = clonedFirstNode.__text.slice(startOffset, endOffset);
|
|
453
|
+
const key = clonedFirstNode.getKey();
|
|
454
|
+
return {
|
|
455
|
+
nodeMap: [[key, clonedFirstNode]],
|
|
456
|
+
range: [key]
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const nodes = selection.getNodes();
|
|
461
|
+
|
|
462
|
+
if (nodes.length === 0) {
|
|
463
|
+
return {
|
|
464
|
+
nodeMap: [],
|
|
465
|
+
range: []
|
|
466
|
+
};
|
|
467
|
+
} // Check if we can use the parent of the nodes, if the
|
|
468
|
+
// parent can't be empty, then it's important that we
|
|
469
|
+
// also copy that element node along with its children.
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
let nodesLength = nodes.length;
|
|
473
|
+
const firstNode = nodes[0];
|
|
474
|
+
const firstNodeParent = firstNode.getParent();
|
|
475
|
+
|
|
476
|
+
if (firstNodeParent !== null && !firstNodeParent.canBeEmpty()) {
|
|
477
|
+
const parentChildren = firstNodeParent.__children;
|
|
478
|
+
const parentChildrenLength = parentChildren.length;
|
|
479
|
+
|
|
480
|
+
if (parentChildrenLength === nodesLength) {
|
|
481
|
+
let areTheSame = true;
|
|
482
|
+
|
|
483
|
+
for (let i = 0; i < parentChildren.length; i++) {
|
|
484
|
+
if (parentChildren[i] !== nodes[i].__key) {
|
|
485
|
+
areTheSame = false;
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (areTheSame) {
|
|
491
|
+
nodesLength++;
|
|
492
|
+
nodes.push(firstNodeParent);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const lastNode = nodes[nodesLength - 1];
|
|
498
|
+
const isBefore = anchor.isBefore(focus);
|
|
499
|
+
const nodeMap = new Map();
|
|
500
|
+
const range = []; // Do first node to root
|
|
501
|
+
|
|
502
|
+
$copyLeafNodeBranchToRoot(firstNode, isBefore ? anchorOffset : focusOffset, true, range, nodeMap); // Copy all nodes between
|
|
503
|
+
|
|
504
|
+
for (let i = 0; i < nodesLength; i++) {
|
|
505
|
+
const node = nodes[i];
|
|
506
|
+
const key = node.getKey();
|
|
507
|
+
|
|
508
|
+
if (!nodeMap.has(key) && (!lexical.$isElementNode(node) || !node.excludeFromCopy())) {
|
|
509
|
+
const clone = $cloneWithProperties(node);
|
|
510
|
+
|
|
511
|
+
if (lexical.$isRootNode(node.getParent())) {
|
|
512
|
+
range.push(node.getKey());
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
nodeMap.set(key, clone);
|
|
516
|
+
}
|
|
517
|
+
} // Do last node to root
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
$copyLeafNodeBranchToRoot(lastNode, isBefore ? focusOffset : anchorOffset, false, range, nodeMap);
|
|
521
|
+
return {
|
|
522
|
+
nodeMap: Array.from(nodeMap.entries()),
|
|
523
|
+
range
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function $moveCaretSelection(selection, isHoldingShift, isBackward, granularity) {
|
|
528
|
+
selection.modify(isHoldingShift ? 'extend' : 'move', isBackward, granularity);
|
|
529
|
+
}
|
|
530
|
+
function $isParentElementRTL(selection) {
|
|
531
|
+
const anchorNode = selection.anchor.getNode();
|
|
532
|
+
const parent = anchorNode.getParentOrThrow();
|
|
533
|
+
return parent.getDirection() === 'rtl';
|
|
534
|
+
}
|
|
535
|
+
function $moveCharacter(selection, isHoldingShift, isBackward) {
|
|
536
|
+
const isRTL = $isParentElementRTL(selection);
|
|
537
|
+
$moveCaretSelection(selection, isHoldingShift, isBackward ? !isRTL : isRTL, 'character');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
542
|
+
*
|
|
543
|
+
* This source code is licensed under the MIT license found in the
|
|
544
|
+
* LICENSE file in the root directory of this source tree.
|
|
545
|
+
*
|
|
546
|
+
*
|
|
547
|
+
*/
|
|
548
|
+
function getPossibleDecoratorNode(focus, isBackward) {
|
|
549
|
+
const focusOffset = focus.offset;
|
|
550
|
+
|
|
551
|
+
if (focus.type === 'element') {
|
|
552
|
+
const block = focus.getNode();
|
|
553
|
+
return block.getChildAtIndex(isBackward ? focusOffset - 1 : focusOffset);
|
|
554
|
+
} else {
|
|
555
|
+
const focusNode = focus.getNode();
|
|
556
|
+
|
|
557
|
+
if (isBackward && focusOffset === 0 || !isBackward && focusOffset === focusNode.getTextContentSize()) {
|
|
558
|
+
return isBackward ? focusNode.getPreviousSibling() : focusNode.getNextSibling();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
567
|
+
*
|
|
568
|
+
* This source code is licensed under the MIT license found in the
|
|
569
|
+
* LICENSE file in the root directory of this source tree.
|
|
570
|
+
*
|
|
571
|
+
*
|
|
572
|
+
*/
|
|
573
|
+
|
|
574
|
+
const isCodeElement = div => {
|
|
575
|
+
return div.style.fontFamily.match('monospace') !== null;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const DOM_NODE_NAME_TO_LEXICAL_NODE = {
|
|
579
|
+
'#text': domNode => ({
|
|
580
|
+
node: lexical.$createTextNode(domNode.textContent)
|
|
581
|
+
}),
|
|
582
|
+
a: domNode => {
|
|
583
|
+
let node;
|
|
584
|
+
|
|
585
|
+
if (domNode instanceof HTMLAnchorElement) {
|
|
586
|
+
node = LinkNode.$createLinkNode(domNode.href);
|
|
587
|
+
} else {
|
|
588
|
+
node = lexical.$createTextNode(domNode.textContent);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
node
|
|
593
|
+
};
|
|
594
|
+
},
|
|
595
|
+
b: domNode => {
|
|
596
|
+
// $FlowFixMe[incompatible-type] domNode is a <b> since we matched it by nodeName
|
|
597
|
+
const b = domNode; // Google Docs wraps all copied HTML in a <b> with font-weight normal
|
|
598
|
+
|
|
599
|
+
const hasNormalFontWeight = b.style.fontWeight === 'normal';
|
|
600
|
+
return {
|
|
601
|
+
forChild: lexicalNode => {
|
|
602
|
+
if (lexical.$isTextNode(lexicalNode) && !hasNormalFontWeight) {
|
|
603
|
+
lexicalNode.toggleFormat('bold');
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
node: null
|
|
607
|
+
};
|
|
608
|
+
},
|
|
609
|
+
br: () => ({
|
|
610
|
+
node: lexical.$createLineBreakNode()
|
|
611
|
+
}),
|
|
612
|
+
div: domNode => {
|
|
613
|
+
// $FlowFixMe[incompatible-type] domNode is a <div> since we matched it by nodeName
|
|
614
|
+
const div = domNode;
|
|
615
|
+
return {
|
|
616
|
+
after: childLexicalNodes => {
|
|
617
|
+
const domParent = domNode.parentNode;
|
|
618
|
+
|
|
619
|
+
if (domParent != null && domNode !== domParent.lastChild) {
|
|
620
|
+
childLexicalNodes.push(lexical.$createLineBreakNode());
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return childLexicalNodes;
|
|
624
|
+
},
|
|
625
|
+
node: isCodeElement(div) ? CodeNode.$createCodeNode() : null
|
|
626
|
+
};
|
|
627
|
+
},
|
|
628
|
+
em: domNode => {
|
|
629
|
+
return {
|
|
630
|
+
forChild: lexicalNode => {
|
|
631
|
+
if (lexical.$isTextNode(lexicalNode)) {
|
|
632
|
+
lexicalNode.toggleFormat('italic');
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
node: null
|
|
636
|
+
};
|
|
637
|
+
},
|
|
638
|
+
h1: () => ({
|
|
639
|
+
node: HeadingNode.$createHeadingNode('h1')
|
|
640
|
+
}),
|
|
641
|
+
h2: () => ({
|
|
642
|
+
node: HeadingNode.$createHeadingNode('h2')
|
|
643
|
+
}),
|
|
644
|
+
h3: () => ({
|
|
645
|
+
node: HeadingNode.$createHeadingNode('h3')
|
|
646
|
+
}),
|
|
647
|
+
h4: () => ({
|
|
648
|
+
node: HeadingNode.$createHeadingNode('h4')
|
|
649
|
+
}),
|
|
650
|
+
h5: () => ({
|
|
651
|
+
node: HeadingNode.$createHeadingNode('h5')
|
|
652
|
+
}),
|
|
653
|
+
i: domNode => {
|
|
654
|
+
return {
|
|
655
|
+
forChild: lexicalNode => {
|
|
656
|
+
if (lexical.$isTextNode(lexicalNode)) {
|
|
657
|
+
lexicalNode.toggleFormat('italic');
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
node: null
|
|
661
|
+
};
|
|
662
|
+
},
|
|
663
|
+
li: () => ({
|
|
664
|
+
node: list.$createListItemNode()
|
|
665
|
+
}),
|
|
666
|
+
ol: () => ({
|
|
667
|
+
node: list.$createListNode('ol')
|
|
668
|
+
}),
|
|
669
|
+
p: () => ({
|
|
670
|
+
node: lexical.$createParagraphNode()
|
|
671
|
+
}),
|
|
672
|
+
pre: domNode => ({
|
|
673
|
+
node: CodeNode.$createCodeNode()
|
|
674
|
+
}),
|
|
675
|
+
span: domNode => {
|
|
676
|
+
// $FlowFixMe[incompatible-type] domNode is a <span> since we matched it by nodeName
|
|
677
|
+
const span = domNode; // Google Docs uses span tags + font-weight for bold text
|
|
678
|
+
|
|
679
|
+
const hasBoldFontWeight = span.style.fontWeight === '700';
|
|
680
|
+
return {
|
|
681
|
+
forChild: lexicalNode => {
|
|
682
|
+
if (lexical.$isTextNode(lexicalNode) && hasBoldFontWeight) {
|
|
683
|
+
lexicalNode.toggleFormat('bold');
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
node: null
|
|
687
|
+
};
|
|
688
|
+
},
|
|
689
|
+
strong: domNode => {
|
|
690
|
+
return {
|
|
691
|
+
forChild: lexicalNode => {
|
|
692
|
+
if (lexical.$isTextNode(lexicalNode)) {
|
|
693
|
+
lexicalNode.toggleFormat('bold');
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
node: null
|
|
697
|
+
};
|
|
698
|
+
},
|
|
699
|
+
table: domNode => {
|
|
700
|
+
// $FlowFixMe[incompatible-type] domNode is a <table> since we matched it by nodeName
|
|
701
|
+
const table = domNode;
|
|
702
|
+
const isGitHubCodeTable = table.classList.contains('js-file-line-container');
|
|
703
|
+
return {
|
|
704
|
+
node: isGitHubCodeTable ? CodeNode.$createCodeNode() : null
|
|
705
|
+
};
|
|
706
|
+
},
|
|
707
|
+
td: domNode => {
|
|
708
|
+
// $FlowFixMe[incompatible-type] domNode is a <table> since we matched it by nodeName
|
|
709
|
+
const cell = domNode;
|
|
710
|
+
const isGitHubCodeCell = cell.classList.contains('js-file-line');
|
|
711
|
+
return {
|
|
712
|
+
after: childLexicalNodes => {
|
|
713
|
+
if (isGitHubCodeCell && cell.parentNode && cell.parentNode.nextSibling) {
|
|
714
|
+
// Append newline between code lines
|
|
715
|
+
childLexicalNodes.push(lexical.$createLineBreakNode());
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return childLexicalNodes;
|
|
719
|
+
},
|
|
720
|
+
node: null
|
|
721
|
+
};
|
|
722
|
+
},
|
|
723
|
+
u: domNode => {
|
|
724
|
+
return {
|
|
725
|
+
forChild: lexicalNode => {
|
|
726
|
+
if (lexical.$isTextNode(lexicalNode)) {
|
|
727
|
+
lexicalNode.toggleFormat('underline');
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
node: null
|
|
731
|
+
};
|
|
732
|
+
},
|
|
733
|
+
ul: () => ({
|
|
734
|
+
node: list.$createListNode('ul')
|
|
735
|
+
})
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
function $generateNodes(nodeRange) {
|
|
739
|
+
const {
|
|
740
|
+
range,
|
|
741
|
+
nodeMap
|
|
742
|
+
} = nodeRange;
|
|
743
|
+
const parsedNodeMap = new Map(nodeMap);
|
|
744
|
+
const nodes = [];
|
|
745
|
+
|
|
746
|
+
for (let i = 0; i < range.length; i++) {
|
|
747
|
+
const key = range[i];
|
|
748
|
+
const parsedNode = parsedNodeMap.get(key);
|
|
749
|
+
|
|
750
|
+
if (parsedNode !== undefined) {
|
|
751
|
+
const node = lexical.$createNodeFromParse(parsedNode, parsedNodeMap);
|
|
752
|
+
nodes.push(node);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return nodes;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function $createNodesFromDOM(node, conversionMap, editor, forChildMap = new Map()) {
|
|
760
|
+
let lexicalNodes = [];
|
|
761
|
+
let currentLexicalNode = null;
|
|
762
|
+
const nodeName = node.nodeName.toLowerCase();
|
|
763
|
+
const customHtmlTransforms = editor._config.htmlTransforms || {};
|
|
764
|
+
const transformFunction = customHtmlTransforms[nodeName] || conversionMap[nodeName];
|
|
765
|
+
const transformOutput = transformFunction ? transformFunction(node) : null;
|
|
766
|
+
let postTransform = null;
|
|
767
|
+
|
|
768
|
+
if (transformOutput !== null) {
|
|
769
|
+
postTransform = transformOutput.after;
|
|
770
|
+
currentLexicalNode = transformOutput.node;
|
|
771
|
+
|
|
772
|
+
if (currentLexicalNode !== null) {
|
|
773
|
+
lexicalNodes.push(currentLexicalNode);
|
|
774
|
+
const forChildFunctions = Array.from(forChildMap.values());
|
|
775
|
+
|
|
776
|
+
for (let i = 0; i < forChildFunctions.length; i++) {
|
|
777
|
+
forChildFunctions[i](currentLexicalNode);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (transformOutput.forChild != null) {
|
|
782
|
+
forChildMap.set(nodeName, transformOutput.forChild);
|
|
783
|
+
}
|
|
784
|
+
} // If the DOM node doesn't have a transformer, we don't know what
|
|
785
|
+
// to do with it but we still need to process any childNodes.
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
const children = node.childNodes;
|
|
789
|
+
let childLexicalNodes = [];
|
|
790
|
+
|
|
791
|
+
for (let i = 0; i < children.length; i++) {
|
|
792
|
+
childLexicalNodes.push(...$createNodesFromDOM(children[i], conversionMap, editor, forChildMap));
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (postTransform != null) {
|
|
796
|
+
childLexicalNodes = postTransform(childLexicalNodes);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (currentLexicalNode == null) {
|
|
800
|
+
// If it hasn't been converted to a LexicalNode, we hoist its children
|
|
801
|
+
// up to the same level as it.
|
|
802
|
+
lexicalNodes = lexicalNodes.concat(childLexicalNodes);
|
|
803
|
+
} else {
|
|
804
|
+
if (lexical.$isElementNode(currentLexicalNode)) {
|
|
805
|
+
// If the current node is a ElementNode after conversion,
|
|
806
|
+
// we can append all the children to it.
|
|
807
|
+
currentLexicalNode.append(...childLexicalNodes);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return lexicalNodes;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function $generateNodesFromDOM(dom, conversionMap, editor) {
|
|
815
|
+
let lexicalNodes = [];
|
|
816
|
+
const elements = dom.body ? Array.from(dom.body.childNodes) : [];
|
|
817
|
+
const elementsLength = elements.length;
|
|
818
|
+
|
|
819
|
+
for (let i = 0; i < elementsLength; i++) {
|
|
820
|
+
const lexicalNode = $createNodesFromDOM(elements[i], conversionMap, editor);
|
|
821
|
+
|
|
822
|
+
if (lexicalNode !== null) {
|
|
823
|
+
lexicalNodes = lexicalNodes.concat(lexicalNode);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return lexicalNodes;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function $insertDataTransferForRichText(dataTransfer, selection, editor) {
|
|
831
|
+
const lexicalNodesString = dataTransfer.getData('application/x-lexical-editor');
|
|
832
|
+
|
|
833
|
+
if (lexicalNodesString) {
|
|
834
|
+
const namespace = editor._config.namespace;
|
|
835
|
+
|
|
836
|
+
try {
|
|
837
|
+
const lexicalClipboardData = JSON.parse(lexicalNodesString);
|
|
838
|
+
|
|
839
|
+
if (lexicalClipboardData.namespace === namespace) {
|
|
840
|
+
const nodeRange = lexicalClipboardData.state;
|
|
841
|
+
const nodes = $generateNodes(nodeRange);
|
|
842
|
+
selection.insertNodes(nodes);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
} catch (e) {// Malformed, missing nodes..
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const textHtmlMimeType = 'text/html';
|
|
850
|
+
const htmlString = dataTransfer.getData(textHtmlMimeType);
|
|
851
|
+
|
|
852
|
+
if (htmlString) {
|
|
853
|
+
const parser = new DOMParser();
|
|
854
|
+
const dom = parser.parseFromString(htmlString, textHtmlMimeType);
|
|
855
|
+
const nodes = $generateNodesFromDOM(dom, DOM_NODE_NAME_TO_LEXICAL_NODE, editor); // Wrap text and inline nodes in paragraph nodes so we have all blocks at the top-level
|
|
856
|
+
|
|
857
|
+
const topLevelBlocks = [];
|
|
858
|
+
let currentBlock = null;
|
|
859
|
+
|
|
860
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
861
|
+
const node = nodes[i];
|
|
862
|
+
|
|
863
|
+
if (!lexical.$isElementNode(node) || node.isInline()) {
|
|
864
|
+
if (currentBlock === null) {
|
|
865
|
+
currentBlock = lexical.$createParagraphNode();
|
|
866
|
+
topLevelBlocks.push(currentBlock);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (currentBlock !== null) {
|
|
870
|
+
currentBlock.append(node);
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
topLevelBlocks.push(node);
|
|
874
|
+
currentBlock = null;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
selection.insertNodes(topLevelBlocks);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
$insertDataTransferForPlainText(dataTransfer, selection);
|
|
883
|
+
}
|
|
884
|
+
function $insertDataTransferForPlainText(dataTransfer, selection) {
|
|
885
|
+
const text = dataTransfer.getData('text/plain');
|
|
886
|
+
|
|
887
|
+
if (text != null) {
|
|
888
|
+
selection.insertRawText(text);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function $shouldOverrideDefaultCharacterSelection(selection, isBackward) {
|
|
892
|
+
const possibleDecoratorNode = getPossibleDecoratorNode(selection.focus, isBackward);
|
|
893
|
+
return lexical.$isDecoratorNode(possibleDecoratorNode);
|
|
894
|
+
}
|
|
895
|
+
function onPasteForRichText(event, editor) {
|
|
896
|
+
event.preventDefault();
|
|
897
|
+
editor.update(() => {
|
|
898
|
+
lexical.$log('onPasteForRichText');
|
|
899
|
+
const selection = lexical.$getSelection();
|
|
900
|
+
const clipboardData = event.clipboardData;
|
|
901
|
+
|
|
902
|
+
if (clipboardData != null && selection !== null) {
|
|
903
|
+
$insertDataTransferForRichText(clipboardData, selection, editor);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
function onCutForRichText(event, editor) {
|
|
908
|
+
onCopyForRichText(event, editor);
|
|
909
|
+
editor.update(() => {
|
|
910
|
+
lexical.$log('onCutForRichText');
|
|
911
|
+
const selection = lexical.$getSelection();
|
|
912
|
+
|
|
913
|
+
if (selection !== null) {
|
|
914
|
+
selection.removeText();
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
function onCopyForRichText(event, editor) {
|
|
919
|
+
event.preventDefault();
|
|
920
|
+
editor.update(() => {
|
|
921
|
+
lexical.$log('onCopyForRichText');
|
|
922
|
+
const clipboardData = event.clipboardData;
|
|
923
|
+
const selection = lexical.$getSelection();
|
|
924
|
+
|
|
925
|
+
if (selection !== null) {
|
|
926
|
+
if (clipboardData != null) {
|
|
927
|
+
const domSelection = window.getSelection(); // If we haven't selected a range, then don't copy anything
|
|
928
|
+
|
|
929
|
+
if (domSelection.isCollapsed) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const range = domSelection.getRangeAt(0);
|
|
934
|
+
|
|
935
|
+
if (range) {
|
|
936
|
+
const container = document.createElement('div');
|
|
937
|
+
const frag = range.cloneContents();
|
|
938
|
+
container.appendChild(frag);
|
|
939
|
+
clipboardData.setData('text/html', container.innerHTML);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
clipboardData.setData('text/plain', selection.getTextContent());
|
|
943
|
+
const namespace = editor._config.namespace;
|
|
944
|
+
clipboardData.setData('application/x-lexical-editor', JSON.stringify({
|
|
945
|
+
namespace,
|
|
946
|
+
state: $cloneContents(selection)
|
|
947
|
+
}));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
955
|
+
*
|
|
956
|
+
* This source code is licensed under the MIT license found in the
|
|
957
|
+
* LICENSE file in the root directory of this source tree.
|
|
958
|
+
*
|
|
959
|
+
*
|
|
960
|
+
*/
|
|
961
|
+
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
965
|
+
*
|
|
966
|
+
* This source code is licensed under the MIT license found in the
|
|
967
|
+
* LICENSE file in the root directory of this source tree.
|
|
968
|
+
*
|
|
969
|
+
*
|
|
970
|
+
*/
|
|
971
|
+
const useLayoutEffectImpl = CAN_USE_DOM ? react.useLayoutEffect : react.useEffect;
|
|
972
|
+
var useLayoutEffect = useLayoutEffectImpl;
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
976
|
+
*
|
|
977
|
+
* This source code is licensed under the MIT license found in the
|
|
978
|
+
* LICENSE file in the root directory of this source tree.
|
|
979
|
+
*
|
|
980
|
+
*
|
|
981
|
+
*/
|
|
982
|
+
function useLexicalDragonSupport(editor) {
|
|
983
|
+
react.useEffect(() => {
|
|
984
|
+
const handler = event => {
|
|
985
|
+
const rootElement = editor.getRootElement();
|
|
986
|
+
|
|
987
|
+
if (document.activeElement !== rootElement) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const data = event.data;
|
|
992
|
+
|
|
993
|
+
if (typeof data === 'string') {
|
|
994
|
+
let parsedData;
|
|
995
|
+
|
|
996
|
+
try {
|
|
997
|
+
parsedData = JSON.parse(data);
|
|
998
|
+
} catch (e) {
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (parsedData && parsedData.protocol === 'nuanria_messaging' && parsedData.type === 'request') {
|
|
1003
|
+
const payload = parsedData.payload;
|
|
1004
|
+
|
|
1005
|
+
if (payload && payload.functionId === 'makeChanges') {
|
|
1006
|
+
const args = payload.args;
|
|
1007
|
+
|
|
1008
|
+
if (args) {
|
|
1009
|
+
const [elementStart, elementLength, text, selStart, selLength, formatCommand] = args; // TODO: we should probably handle formatCommand somehow?
|
|
1010
|
+
editor.update(() => {
|
|
1011
|
+
lexical.$log('useLexicalDragonSupport');
|
|
1012
|
+
const selection = lexical.$getSelection();
|
|
1013
|
+
|
|
1014
|
+
if (selection !== null) {
|
|
1015
|
+
const anchor = selection.anchor;
|
|
1016
|
+
let anchorNode = anchor.getNode();
|
|
1017
|
+
let setSelStart = 0;
|
|
1018
|
+
let setSelEnd = 0;
|
|
1019
|
+
|
|
1020
|
+
if (lexical.$isTextNode(anchorNode)) {
|
|
1021
|
+
// set initial selection
|
|
1022
|
+
if (elementStart >= 0 && elementLength >= 0) {
|
|
1023
|
+
setSelStart = elementStart;
|
|
1024
|
+
setSelEnd = elementStart + elementLength; // If the offset is more than the end, make it the end
|
|
1025
|
+
|
|
1026
|
+
selection.setTextNodeRange(anchorNode, setSelStart, anchorNode, setSelEnd);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (setSelStart !== setSelEnd || text !== '') {
|
|
1031
|
+
selection.insertRawText(text);
|
|
1032
|
+
anchorNode = anchor.getNode();
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (lexical.$isTextNode(anchorNode)) {
|
|
1036
|
+
// set final selection
|
|
1037
|
+
setSelStart = selStart;
|
|
1038
|
+
setSelEnd = selStart + selLength;
|
|
1039
|
+
const anchorNodeTextLength = anchorNode.getTextContentSize(); // If the offset is more than the end, make it the end
|
|
1040
|
+
|
|
1041
|
+
setSelStart = setSelStart > anchorNodeTextLength ? anchorNodeTextLength : setSelStart;
|
|
1042
|
+
setSelEnd = setSelEnd > anchorNodeTextLength ? anchorNodeTextLength : setSelEnd;
|
|
1043
|
+
selection.setTextNodeRange(anchorNode, setSelStart, anchorNode, setSelEnd);
|
|
1044
|
+
} // block the chrome extension from handling this event
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
event.stopImmediatePropagation();
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
window.addEventListener('message', handler, true);
|
|
1057
|
+
return () => {
|
|
1058
|
+
window.removeEventListener('message', handler, true);
|
|
1059
|
+
};
|
|
1060
|
+
}, [editor]);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1065
|
+
*
|
|
1066
|
+
* This source code is licensed under the MIT license found in the
|
|
1067
|
+
* LICENSE file in the root directory of this source tree.
|
|
1068
|
+
*
|
|
1069
|
+
*
|
|
1070
|
+
*/
|
|
1071
|
+
const EditorPriority = 0;
|
|
1072
|
+
function useRichTextSetup(editor) {
|
|
1073
|
+
useLayoutEffect(() => {
|
|
1074
|
+
const removeListener = editor.addListener('command', (type, payload) => {
|
|
1075
|
+
const selection = lexical.$getSelection();
|
|
1076
|
+
|
|
1077
|
+
if (selection === null) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
switch (type) {
|
|
1082
|
+
case 'deleteCharacter':
|
|
1083
|
+
{
|
|
1084
|
+
const isBackward = payload;
|
|
1085
|
+
selection.deleteCharacter(isBackward);
|
|
1086
|
+
return true;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
case 'deleteWord':
|
|
1090
|
+
{
|
|
1091
|
+
const isBackward = payload;
|
|
1092
|
+
selection.deleteWord(isBackward);
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
case 'deleteLine':
|
|
1097
|
+
{
|
|
1098
|
+
const isBackward = payload;
|
|
1099
|
+
selection.deleteLine(isBackward);
|
|
1100
|
+
return true;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
case 'insertText':
|
|
1104
|
+
{
|
|
1105
|
+
const eventOrText = payload;
|
|
1106
|
+
|
|
1107
|
+
if (typeof eventOrText === 'string') {
|
|
1108
|
+
selection.insertText(eventOrText);
|
|
1109
|
+
} else {
|
|
1110
|
+
const dataTransfer = eventOrText.dataTransfer;
|
|
1111
|
+
|
|
1112
|
+
if (dataTransfer != null) {
|
|
1113
|
+
$insertDataTransferForRichText(dataTransfer, selection, editor);
|
|
1114
|
+
} else {
|
|
1115
|
+
const data = eventOrText.data;
|
|
1116
|
+
|
|
1117
|
+
if (data) {
|
|
1118
|
+
selection.insertText(data);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
case 'removeText':
|
|
1127
|
+
selection.removeText();
|
|
1128
|
+
return true;
|
|
1129
|
+
|
|
1130
|
+
case 'formatText':
|
|
1131
|
+
{
|
|
1132
|
+
const format = payload;
|
|
1133
|
+
selection.formatText(format);
|
|
1134
|
+
return true;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
case 'formatElement':
|
|
1138
|
+
{
|
|
1139
|
+
const format = payload;
|
|
1140
|
+
const node = selection.anchor.getNode();
|
|
1141
|
+
const element = lexical.$isElementNode(node) ? node : node.getParentOrThrow();
|
|
1142
|
+
element.setFormat(format);
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
case 'insertLineBreak':
|
|
1147
|
+
const selectStart = payload;
|
|
1148
|
+
selection.insertLineBreak(selectStart);
|
|
1149
|
+
return true;
|
|
1150
|
+
|
|
1151
|
+
case 'insertParagraph':
|
|
1152
|
+
selection.insertParagraph();
|
|
1153
|
+
return true;
|
|
1154
|
+
|
|
1155
|
+
case 'indentContent':
|
|
1156
|
+
{
|
|
1157
|
+
// Handle code blocks
|
|
1158
|
+
const anchor = selection.anchor;
|
|
1159
|
+
const parentBlock = anchor.type === 'element' ? anchor.getNode() : anchor.getNode().getParentOrThrow();
|
|
1160
|
+
|
|
1161
|
+
if (parentBlock.canInsertTab()) {
|
|
1162
|
+
editor.execCommand('insertText', '\t');
|
|
1163
|
+
} else {
|
|
1164
|
+
if (parentBlock.getIndent() !== 10) {
|
|
1165
|
+
parentBlock.setIndent(parentBlock.getIndent() + 1);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
return true;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
case 'outdentContent':
|
|
1173
|
+
{
|
|
1174
|
+
// Handle code blocks
|
|
1175
|
+
const anchor = selection.anchor;
|
|
1176
|
+
const anchorNode = anchor.getNode();
|
|
1177
|
+
const parentBlock = anchor.type === 'element' ? anchor.getNode() : anchor.getNode().getParentOrThrow();
|
|
1178
|
+
|
|
1179
|
+
if (parentBlock.canInsertTab()) {
|
|
1180
|
+
const textContent = anchorNode.getTextContent();
|
|
1181
|
+
const character = textContent[anchor.offset - 1];
|
|
1182
|
+
|
|
1183
|
+
if (character === '\t') {
|
|
1184
|
+
editor.execCommand('deleteCharacter', true);
|
|
1185
|
+
}
|
|
1186
|
+
} else {
|
|
1187
|
+
if (parentBlock.getIndent() !== 0) {
|
|
1188
|
+
parentBlock.setIndent(parentBlock.getIndent() - 1);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
return true;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
case 'keyArrowLeft':
|
|
1196
|
+
{
|
|
1197
|
+
const event = payload;
|
|
1198
|
+
const isHoldingShift = event.shiftKey;
|
|
1199
|
+
|
|
1200
|
+
if ($shouldOverrideDefaultCharacterSelection(selection, true)) {
|
|
1201
|
+
event.preventDefault();
|
|
1202
|
+
$moveCharacter(selection, isHoldingShift, true);
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
case 'keyArrowRight':
|
|
1210
|
+
{
|
|
1211
|
+
const event = payload;
|
|
1212
|
+
const isHoldingShift = event.shiftKey;
|
|
1213
|
+
|
|
1214
|
+
if ($shouldOverrideDefaultCharacterSelection(selection, false)) {
|
|
1215
|
+
event.preventDefault();
|
|
1216
|
+
$moveCharacter(selection, isHoldingShift, false);
|
|
1217
|
+
return true;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return false;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
case 'keyBackspace':
|
|
1224
|
+
{
|
|
1225
|
+
const event = payload;
|
|
1226
|
+
event.preventDefault();
|
|
1227
|
+
const {
|
|
1228
|
+
anchor
|
|
1229
|
+
} = selection;
|
|
1230
|
+
|
|
1231
|
+
if (selection.isCollapsed() && anchor.offset === 0) {
|
|
1232
|
+
const element = anchor.type === 'element' ? anchor.getNode() : anchor.getNode().getParentOrThrow();
|
|
1233
|
+
|
|
1234
|
+
if (element.getIndent() > 0) {
|
|
1235
|
+
return editor.execCommand('outdentContent');
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
return editor.execCommand('deleteCharacter', true);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
case 'keyDelete':
|
|
1243
|
+
{
|
|
1244
|
+
const event = payload;
|
|
1245
|
+
event.preventDefault();
|
|
1246
|
+
return editor.execCommand('deleteCharacter', false);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
case 'keyEnter':
|
|
1250
|
+
{
|
|
1251
|
+
const event = payload;
|
|
1252
|
+
event.preventDefault();
|
|
1253
|
+
|
|
1254
|
+
if (event.shiftKey) {
|
|
1255
|
+
return editor.execCommand('insertLineBreak');
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
return editor.execCommand('insertParagraph');
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
case 'keyTab':
|
|
1262
|
+
{
|
|
1263
|
+
const event = payload;
|
|
1264
|
+
event.preventDefault();
|
|
1265
|
+
return editor.execCommand(event.shiftKey ? 'outdentContent' : 'indentContent');
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
case 'keyEscape':
|
|
1269
|
+
{
|
|
1270
|
+
editor.blur();
|
|
1271
|
+
return true;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
case 'copy':
|
|
1275
|
+
{
|
|
1276
|
+
const event = payload;
|
|
1277
|
+
onCopyForRichText(event, editor);
|
|
1278
|
+
return true;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
case 'cut':
|
|
1282
|
+
{
|
|
1283
|
+
const event = payload;
|
|
1284
|
+
onCutForRichText(event, editor);
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
case 'paste':
|
|
1289
|
+
{
|
|
1290
|
+
const event = payload;
|
|
1291
|
+
onPasteForRichText(event, editor);
|
|
1292
|
+
return true;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
case 'drop':
|
|
1296
|
+
case 'dragstart':
|
|
1297
|
+
{
|
|
1298
|
+
// TODO: Make drag and drop work at some point.
|
|
1299
|
+
const event = payload;
|
|
1300
|
+
event.preventDefault();
|
|
1301
|
+
return true;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
return false;
|
|
1306
|
+
}, EditorPriority);
|
|
1307
|
+
editor.execCommand('bootstrapEditor');
|
|
1308
|
+
return removeListener;
|
|
1309
|
+
}, [editor]);
|
|
1310
|
+
useLexicalDragonSupport(editor);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1315
|
+
*
|
|
1316
|
+
* This source code is licensed under the MIT license found in the
|
|
1317
|
+
* LICENSE file in the root directory of this source tree.
|
|
1318
|
+
*
|
|
1319
|
+
*
|
|
1320
|
+
*/
|
|
1321
|
+
function useLexicalRichText(editor, externalHistoryState) {
|
|
1322
|
+
useRichTextSetup(editor);
|
|
1323
|
+
useLexicalHistory(editor, externalHistoryState);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
module.exports = useLexicalRichText;
|