@lexical/clipboard 0.12.2 → 0.12.4
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/LexicalClipboard.dev.js +44 -110
- package/LexicalClipboard.prod.js +12 -12
- package/package.json +6 -6
package/LexicalClipboard.dev.js
CHANGED
|
@@ -18,6 +18,7 @@ var lexical = require('lexical');
|
|
|
18
18
|
* LICENSE file in the root directory of this source tree.
|
|
19
19
|
*
|
|
20
20
|
*/
|
|
21
|
+
|
|
21
22
|
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -27,8 +28,8 @@ const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !==
|
|
|
27
28
|
* LICENSE file in the root directory of this source tree.
|
|
28
29
|
*
|
|
29
30
|
*/
|
|
30
|
-
|
|
31
31
|
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
|
32
|
+
|
|
32
33
|
/**
|
|
33
34
|
* Returns the *currently selected* Lexical content as an HTML string, relying on the
|
|
34
35
|
* logic defined in the exportDOM methods on the LexicalNode classes. Note that
|
|
@@ -38,24 +39,21 @@ const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).g
|
|
|
38
39
|
* @param editor - LexicalEditor instance to get HTML content from
|
|
39
40
|
* @returns a string of HTML content
|
|
40
41
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
42
|
function $getHtmlContent(editor) {
|
|
44
43
|
const selection = lexical.$getSelection();
|
|
45
|
-
|
|
46
44
|
if (selection == null) {
|
|
47
45
|
{
|
|
48
46
|
throw Error(`Expected valid LexicalSelection`);
|
|
49
47
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
48
|
+
}
|
|
52
49
|
|
|
50
|
+
// If we haven't selected anything
|
|
53
51
|
if (lexical.$isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
|
|
54
52
|
return '';
|
|
55
53
|
}
|
|
56
|
-
|
|
57
54
|
return html.$generateHtmlFromNodes(editor, selection);
|
|
58
55
|
}
|
|
56
|
+
|
|
59
57
|
/**
|
|
60
58
|
* Returns the *currently selected* Lexical content as a JSON string, relying on the
|
|
61
59
|
* logic defined in the exportJSON methods on the LexicalNode classes. Note that
|
|
@@ -65,23 +63,21 @@ function $getHtmlContent(editor) {
|
|
|
65
63
|
* @param editor - LexicalEditor instance to get the JSON content from
|
|
66
64
|
* @returns
|
|
67
65
|
*/
|
|
68
|
-
|
|
69
66
|
function $getLexicalContent(editor) {
|
|
70
67
|
const selection = lexical.$getSelection();
|
|
71
|
-
|
|
72
68
|
if (selection == null) {
|
|
73
69
|
{
|
|
74
70
|
throw Error(`Expected valid LexicalSelection`);
|
|
75
71
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
72
|
+
}
|
|
78
73
|
|
|
74
|
+
// If we haven't selected anything
|
|
79
75
|
if (lexical.$isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
|
|
80
76
|
return null;
|
|
81
77
|
}
|
|
82
|
-
|
|
83
78
|
return JSON.stringify($generateJSONFromSelectedNodes(editor, selection));
|
|
84
79
|
}
|
|
80
|
+
|
|
85
81
|
/**
|
|
86
82
|
* Attempts to insert content of the mime-types text/plain or text/uri-list from
|
|
87
83
|
* the provided DataTransfer object into the editor at the provided selection.
|
|
@@ -90,14 +86,13 @@ function $getLexicalContent(editor) {
|
|
|
90
86
|
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
|
|
91
87
|
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
|
|
92
88
|
*/
|
|
93
|
-
|
|
94
89
|
function $insertDataTransferForPlainText(dataTransfer, selection) {
|
|
95
90
|
const text = dataTransfer.getData('text/plain') || dataTransfer.getData('text/uri-list');
|
|
96
|
-
|
|
97
91
|
if (text != null) {
|
|
98
92
|
selection.insertRawText(text);
|
|
99
93
|
}
|
|
100
94
|
}
|
|
95
|
+
|
|
101
96
|
/**
|
|
102
97
|
* Attempts to insert content of the mime-types application/x-lexical-editor, text/html,
|
|
103
98
|
* text/plain, or text/uri-list (in descending order of priority) from the provided DataTransfer
|
|
@@ -107,47 +102,43 @@ function $insertDataTransferForPlainText(dataTransfer, selection) {
|
|
|
107
102
|
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
|
|
108
103
|
* @param editor the LexicalEditor the content is being inserted into.
|
|
109
104
|
*/
|
|
110
|
-
|
|
111
105
|
function $insertDataTransferForRichText(dataTransfer, selection, editor) {
|
|
112
106
|
const lexicalString = dataTransfer.getData('application/x-lexical-editor');
|
|
113
|
-
|
|
114
107
|
if (lexicalString) {
|
|
115
108
|
try {
|
|
116
109
|
const payload = JSON.parse(lexicalString);
|
|
117
|
-
|
|
118
110
|
if (payload.namespace === editor._config.namespace && Array.isArray(payload.nodes)) {
|
|
119
111
|
const nodes = $generateNodesFromSerializedNodes(payload.nodes);
|
|
120
112
|
return $insertGeneratedNodes(editor, nodes, selection);
|
|
121
113
|
}
|
|
122
|
-
} catch {
|
|
114
|
+
} catch (_unused) {
|
|
115
|
+
// Fail silently.
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
|
-
|
|
126
118
|
const htmlString = dataTransfer.getData('text/html');
|
|
127
|
-
|
|
128
119
|
if (htmlString) {
|
|
129
120
|
try {
|
|
130
121
|
const parser = new DOMParser();
|
|
131
122
|
const dom = parser.parseFromString(htmlString, 'text/html');
|
|
132
123
|
const nodes = html.$generateNodesFromDOM(editor, dom);
|
|
133
124
|
return $insertGeneratedNodes(editor, nodes, selection);
|
|
134
|
-
} catch {
|
|
125
|
+
} catch (_unused2) {
|
|
126
|
+
// Fail silently.
|
|
135
127
|
}
|
|
136
|
-
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Multi-line plain text in rich text mode pasted as separate paragraphs
|
|
137
131
|
// instead of single paragraph with linebreaks.
|
|
138
132
|
// Webkit-specific: Supports read 'text/uri-list' in clipboard.
|
|
139
|
-
|
|
140
|
-
|
|
141
133
|
const text = dataTransfer.getData('text/plain') || dataTransfer.getData('text/uri-list');
|
|
142
|
-
|
|
143
134
|
if (text != null) {
|
|
144
135
|
if (lexical.$isRangeSelection(selection)) {
|
|
145
136
|
const parts = text.split(/(\r?\n|\t)/);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
137
|
+
if (parts[parts.length - 1] === '') {
|
|
138
|
+
parts.pop();
|
|
139
|
+
}
|
|
140
|
+
for (let i = 0; i < parts.length; i++) {
|
|
149
141
|
const part = parts[i];
|
|
150
|
-
|
|
151
142
|
if (part === '\n' || part === '\r\n') {
|
|
152
143
|
selection.insertParagraph();
|
|
153
144
|
} else if (part === '\t') {
|
|
@@ -161,6 +152,7 @@ function $insertDataTransferForRichText(dataTransfer, selection, editor) {
|
|
|
161
152
|
}
|
|
162
153
|
}
|
|
163
154
|
}
|
|
155
|
+
|
|
164
156
|
/**
|
|
165
157
|
* Inserts Lexical nodes into the editor using different strategies depending on
|
|
166
158
|
* some simple selection-based heuristics. If you're looking for a generic way to
|
|
@@ -171,39 +163,32 @@ function $insertDataTransferForRichText(dataTransfer, selection, editor) {
|
|
|
171
163
|
* @param nodes The nodes to insert.
|
|
172
164
|
* @param selection The selection to insert the nodes into.
|
|
173
165
|
*/
|
|
174
|
-
|
|
175
166
|
function $insertGeneratedNodes(editor, nodes, selection) {
|
|
176
167
|
const isSelectionInsideOfGrid = lexical.DEPRECATED_$isGridSelection(selection) || utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n)) !== null && utils.$findMatchingParent(selection.focus.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n)) !== null;
|
|
177
|
-
|
|
178
168
|
if (isSelectionInsideOfGrid && nodes.length === 1 && lexical.DEPRECATED_$isGridNode(nodes[0])) {
|
|
179
169
|
$mergeGridNodesStrategy(nodes, selection, false, editor);
|
|
180
170
|
return;
|
|
181
171
|
}
|
|
182
|
-
|
|
183
172
|
$basicInsertStrategy(nodes, selection);
|
|
184
173
|
return;
|
|
185
174
|
}
|
|
186
|
-
|
|
187
175
|
function $basicInsertStrategy(nodes, selection) {
|
|
188
176
|
// Wrap text and inline nodes in paragraph nodes so we have all blocks at the top-level
|
|
189
177
|
const topLevelBlocks = [];
|
|
190
178
|
let currentBlock = null;
|
|
191
|
-
|
|
192
179
|
for (let i = 0; i < nodes.length; i++) {
|
|
193
180
|
const node = nodes[i];
|
|
194
181
|
const isLineBreakNode = lexical.$isLineBreakNode(node);
|
|
195
|
-
|
|
196
182
|
if (isLineBreakNode || lexical.$isDecoratorNode(node) && node.isInline() || lexical.$isElementNode(node) && node.isInline() || lexical.$isTextNode(node) || node.isParentRequired()) {
|
|
197
183
|
if (currentBlock === null) {
|
|
198
184
|
currentBlock = node.createParentElementNode();
|
|
199
|
-
topLevelBlocks.push(currentBlock);
|
|
185
|
+
topLevelBlocks.push(currentBlock);
|
|
186
|
+
// In the case of LineBreakNode, we just need to
|
|
200
187
|
// add an empty ParagraphNode to the topLevelBlocks.
|
|
201
|
-
|
|
202
188
|
if (isLineBreakNode) {
|
|
203
189
|
continue;
|
|
204
190
|
}
|
|
205
191
|
}
|
|
206
|
-
|
|
207
192
|
if (currentBlock !== null) {
|
|
208
193
|
currentBlock.append(node);
|
|
209
194
|
}
|
|
@@ -212,30 +197,25 @@ function $basicInsertStrategy(nodes, selection) {
|
|
|
212
197
|
currentBlock = null;
|
|
213
198
|
}
|
|
214
199
|
}
|
|
215
|
-
|
|
216
200
|
if (lexical.$isRangeSelection(selection)) {
|
|
217
201
|
selection.insertNodes(topLevelBlocks);
|
|
218
202
|
} else if (lexical.DEPRECATED_$isGridSelection(selection)) {
|
|
219
203
|
// If there's an active grid selection and a non grid is pasted, add to the anchor.
|
|
220
204
|
const anchorCell = selection.anchor.getNode();
|
|
221
|
-
|
|
222
205
|
if (!lexical.DEPRECATED_$isGridCellNode(anchorCell)) {
|
|
223
206
|
{
|
|
224
207
|
throw Error(`Expected Grid Cell in Grid Selection`);
|
|
225
208
|
}
|
|
226
209
|
}
|
|
227
|
-
|
|
228
210
|
anchorCell.append(...topLevelBlocks);
|
|
229
211
|
}
|
|
230
212
|
}
|
|
231
|
-
|
|
232
213
|
function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
|
|
233
214
|
if (nodes.length !== 1 || !lexical.DEPRECATED_$isGridNode(nodes[0])) {
|
|
234
215
|
{
|
|
235
216
|
throw Error(`$mergeGridNodesStrategy: Expected Grid insertion.`);
|
|
236
217
|
}
|
|
237
218
|
}
|
|
238
|
-
|
|
239
219
|
const newGrid = nodes[0];
|
|
240
220
|
const newGridRows = newGrid.getChildren();
|
|
241
221
|
const newColumnCount = newGrid.getFirstChildOrThrow().getChildrenSize();
|
|
@@ -243,13 +223,11 @@ function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
|
|
|
243
223
|
const gridCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n));
|
|
244
224
|
const gridRowNode = gridCellNode && utils.$findMatchingParent(gridCellNode, n => lexical.DEPRECATED_$isGridRowNode(n));
|
|
245
225
|
const gridNode = gridRowNode && utils.$findMatchingParent(gridRowNode, n => lexical.DEPRECATED_$isGridNode(n));
|
|
246
|
-
|
|
247
226
|
if (!lexical.DEPRECATED_$isGridCellNode(gridCellNode) || !lexical.DEPRECATED_$isGridRowNode(gridRowNode) || !lexical.DEPRECATED_$isGridNode(gridNode)) {
|
|
248
227
|
{
|
|
249
228
|
throw Error(`$mergeGridNodesStrategy: Expected selection to be inside of a Grid.`);
|
|
250
229
|
}
|
|
251
230
|
}
|
|
252
|
-
|
|
253
231
|
const startY = gridRowNode.getIndexWithinParent();
|
|
254
232
|
const stopY = Math.min(gridNode.getChildrenSize() - 1, startY + newRowCount - 1);
|
|
255
233
|
const startX = gridCellNode.getIndexWithinParent();
|
|
@@ -262,51 +240,40 @@ function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
|
|
|
262
240
|
let newRowIdx = 0;
|
|
263
241
|
let newAnchorCellKey;
|
|
264
242
|
let newFocusCellKey;
|
|
265
|
-
|
|
266
243
|
for (let r = fromY; r <= toY; r++) {
|
|
267
244
|
const currentGridRowNode = gridRowNodes[r];
|
|
268
|
-
|
|
269
245
|
if (!lexical.DEPRECATED_$isGridRowNode(currentGridRowNode)) {
|
|
270
246
|
{
|
|
271
247
|
throw Error(`getNodes: expected to find GridRowNode`);
|
|
272
248
|
}
|
|
273
249
|
}
|
|
274
|
-
|
|
275
250
|
const newGridRowNode = newGridRows[newRowIdx];
|
|
276
|
-
|
|
277
251
|
if (!lexical.DEPRECATED_$isGridRowNode(newGridRowNode)) {
|
|
278
252
|
{
|
|
279
253
|
throw Error(`getNodes: expected to find GridRowNode`);
|
|
280
254
|
}
|
|
281
255
|
}
|
|
282
|
-
|
|
283
256
|
const gridCellNodes = currentGridRowNode.getChildren();
|
|
284
257
|
const newGridCellNodes = newGridRowNode.getChildren();
|
|
285
258
|
let newColumnIdx = 0;
|
|
286
|
-
|
|
287
259
|
for (let c = fromX; c <= toX; c++) {
|
|
288
260
|
const currentGridCellNode = gridCellNodes[c];
|
|
289
|
-
|
|
290
261
|
if (!lexical.DEPRECATED_$isGridCellNode(currentGridCellNode)) {
|
|
291
262
|
{
|
|
292
263
|
throw Error(`getNodes: expected to find GridCellNode`);
|
|
293
264
|
}
|
|
294
265
|
}
|
|
295
|
-
|
|
296
266
|
const newGridCellNode = newGridCellNodes[newColumnIdx];
|
|
297
|
-
|
|
298
267
|
if (!lexical.DEPRECATED_$isGridCellNode(newGridCellNode)) {
|
|
299
268
|
{
|
|
300
269
|
throw Error(`getNodes: expected to find GridCellNode`);
|
|
301
270
|
}
|
|
302
271
|
}
|
|
303
|
-
|
|
304
272
|
if (r === fromY && c === fromX) {
|
|
305
273
|
newAnchorCellKey = currentGridCellNode.getKey();
|
|
306
274
|
} else if (r === toY && c === toX) {
|
|
307
275
|
newFocusCellKey = currentGridCellNode.getKey();
|
|
308
276
|
}
|
|
309
|
-
|
|
310
277
|
const originalChildren = currentGridCellNode.getChildren();
|
|
311
278
|
newGridCellNode.getChildren().forEach(child => {
|
|
312
279
|
if (lexical.$isTextNode(child)) {
|
|
@@ -320,10 +287,8 @@ function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
|
|
|
320
287
|
originalChildren.forEach(n => n.remove());
|
|
321
288
|
newColumnIdx++;
|
|
322
289
|
}
|
|
323
|
-
|
|
324
290
|
newRowIdx++;
|
|
325
291
|
}
|
|
326
|
-
|
|
327
292
|
if (newAnchorCellKey && newFocusCellKey) {
|
|
328
293
|
const newGridSelection = lexical.DEPRECATED_$createGridSelection();
|
|
329
294
|
newGridSelection.set(gridNode.getKey(), newAnchorCellKey, newFocusCellKey);
|
|
@@ -331,20 +296,19 @@ function $mergeGridNodesStrategy(nodes, selection, isFromLexical, editor) {
|
|
|
331
296
|
editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
|
|
332
297
|
}
|
|
333
298
|
}
|
|
334
|
-
|
|
335
299
|
function exportNodeToJSON(node) {
|
|
336
300
|
const serializedNode = node.exportJSON();
|
|
337
|
-
const nodeClass = node.constructor;
|
|
301
|
+
const nodeClass = node.constructor;
|
|
338
302
|
|
|
303
|
+
// @ts-expect-error TODO Replace Class utility type with InstanceType
|
|
339
304
|
if (serializedNode.type !== nodeClass.getType()) {
|
|
340
305
|
{
|
|
341
306
|
throw Error(`LexicalNode: Node ${nodeClass.name} does not implement .exportJSON().`);
|
|
342
307
|
}
|
|
343
|
-
}
|
|
344
|
-
|
|
308
|
+
}
|
|
345
309
|
|
|
310
|
+
// @ts-expect-error TODO Replace Class utility type with InstanceType
|
|
346
311
|
const serializedChildren = serializedNode.children;
|
|
347
|
-
|
|
348
312
|
if (lexical.$isElementNode(node)) {
|
|
349
313
|
if (!Array.isArray(serializedChildren)) {
|
|
350
314
|
{
|
|
@@ -352,50 +316,44 @@ function exportNodeToJSON(node) {
|
|
|
352
316
|
}
|
|
353
317
|
}
|
|
354
318
|
}
|
|
355
|
-
|
|
356
319
|
return serializedNode;
|
|
357
320
|
}
|
|
358
|
-
|
|
359
321
|
function $appendNodesToJSON(editor, selection$1, currentNode, targetArray = []) {
|
|
360
322
|
let shouldInclude = selection$1 != null ? currentNode.isSelected(selection$1) : true;
|
|
361
323
|
const shouldExclude = lexical.$isElementNode(currentNode) && currentNode.excludeFromCopy('html');
|
|
362
324
|
let target = currentNode;
|
|
363
|
-
|
|
364
325
|
if (selection$1 !== null) {
|
|
365
326
|
let clone = selection.$cloneWithProperties(currentNode);
|
|
366
327
|
clone = lexical.$isTextNode(clone) && selection$1 != null ? selection.$sliceSelectedTextNodeContent(selection$1, clone) : clone;
|
|
367
328
|
target = clone;
|
|
368
329
|
}
|
|
369
|
-
|
|
370
330
|
const children = lexical.$isElementNode(target) ? target.getChildren() : [];
|
|
371
|
-
const serializedNode = exportNodeToJSON(target);
|
|
331
|
+
const serializedNode = exportNodeToJSON(target);
|
|
332
|
+
|
|
333
|
+
// TODO: TextNode calls getTextContent() (NOT node.__text) within it's exportJSON method
|
|
372
334
|
// which uses getLatest() to get the text from the original node with the same key.
|
|
373
335
|
// This is a deeper issue with the word "clone" here, it's still a reference to the
|
|
374
336
|
// same node as far as the LexicalEditor is concerned since it shares a key.
|
|
375
337
|
// We need a way to create a clone of a Node in memory with it's own key, but
|
|
376
338
|
// until then this hack will work for the selected text extract use case.
|
|
377
|
-
|
|
378
339
|
if (lexical.$isTextNode(target)) {
|
|
379
|
-
const text = target.__text;
|
|
340
|
+
const text = target.__text;
|
|
341
|
+
// If an uncollapsed selection ends or starts at the end of a line of specialized,
|
|
380
342
|
// TextNodes, such as code tokens, we will get a 'blank' TextNode here, i.e., one
|
|
381
343
|
// with text of length 0. We don't want this, it makes a confusing mess. Reset!
|
|
382
|
-
|
|
383
344
|
if (text.length > 0) {
|
|
384
345
|
serializedNode.text = text;
|
|
385
346
|
} else {
|
|
386
347
|
shouldInclude = false;
|
|
387
348
|
}
|
|
388
349
|
}
|
|
389
|
-
|
|
390
350
|
for (let i = 0; i < children.length; i++) {
|
|
391
351
|
const childNode = children[i];
|
|
392
352
|
const shouldIncludeChild = $appendNodesToJSON(editor, selection$1, childNode, serializedNode.children);
|
|
393
|
-
|
|
394
353
|
if (!shouldInclude && lexical.$isElementNode(currentNode) && shouldIncludeChild && currentNode.extractWithChild(childNode, selection$1, 'clone')) {
|
|
395
354
|
shouldInclude = true;
|
|
396
355
|
}
|
|
397
356
|
}
|
|
398
|
-
|
|
399
357
|
if (shouldInclude && !shouldExclude) {
|
|
400
358
|
targetArray.push(serializedNode);
|
|
401
359
|
} else if (Array.isArray(serializedNode.children)) {
|
|
@@ -404,10 +362,10 @@ function $appendNodesToJSON(editor, selection$1, currentNode, targetArray = [])
|
|
|
404
362
|
targetArray.push(serializedChildNode);
|
|
405
363
|
}
|
|
406
364
|
}
|
|
407
|
-
|
|
408
365
|
return shouldInclude;
|
|
409
|
-
}
|
|
366
|
+
}
|
|
410
367
|
|
|
368
|
+
// TODO why $ function with Editor instance?
|
|
411
369
|
/**
|
|
412
370
|
* Gets the Lexical JSON of the nodes inside the provided Selection.
|
|
413
371
|
*
|
|
@@ -415,23 +373,20 @@ function $appendNodesToJSON(editor, selection$1, currentNode, targetArray = [])
|
|
|
415
373
|
* @param selection Selection to get the JSON content from.
|
|
416
374
|
* @returns an object with the editor namespace and a list of serializable nodes as JavaScript objects.
|
|
417
375
|
*/
|
|
418
|
-
|
|
419
|
-
|
|
420
376
|
function $generateJSONFromSelectedNodes(editor, selection) {
|
|
421
377
|
const nodes = [];
|
|
422
378
|
const root = lexical.$getRoot();
|
|
423
379
|
const topLevelChildren = root.getChildren();
|
|
424
|
-
|
|
425
380
|
for (let i = 0; i < topLevelChildren.length; i++) {
|
|
426
381
|
const topLevelNode = topLevelChildren[i];
|
|
427
382
|
$appendNodesToJSON(editor, selection, topLevelNode, nodes);
|
|
428
383
|
}
|
|
429
|
-
|
|
430
384
|
return {
|
|
431
385
|
namespace: editor._config.namespace,
|
|
432
386
|
nodes
|
|
433
387
|
};
|
|
434
388
|
}
|
|
389
|
+
|
|
435
390
|
/**
|
|
436
391
|
* This method takes an array of objects conforming to the BaseSeralizedNode interface and returns
|
|
437
392
|
* an Array containing instances of the corresponding LexicalNode classes registered on the editor.
|
|
@@ -440,27 +395,23 @@ function $generateJSONFromSelectedNodes(editor, selection) {
|
|
|
440
395
|
* @param serializedNodes an Array of objects conforming to the BaseSerializedNode interface.
|
|
441
396
|
* @returns an Array of Lexical Node objects.
|
|
442
397
|
*/
|
|
443
|
-
|
|
444
398
|
function $generateNodesFromSerializedNodes(serializedNodes) {
|
|
445
399
|
const nodes = [];
|
|
446
|
-
|
|
447
400
|
for (let i = 0; i < serializedNodes.length; i++) {
|
|
448
401
|
const serializedNode = serializedNodes[i];
|
|
449
402
|
const node = lexical.$parseSerializedNode(serializedNode);
|
|
450
|
-
|
|
451
403
|
if (lexical.$isTextNode(node)) {
|
|
452
404
|
selection.$addNodeStyle(node);
|
|
453
405
|
}
|
|
454
|
-
|
|
455
406
|
nodes.push(node);
|
|
456
407
|
}
|
|
457
|
-
|
|
458
408
|
return nodes;
|
|
459
409
|
}
|
|
460
410
|
const EVENT_LATENCY = 50;
|
|
461
|
-
let clipboardEventTimeout = null;
|
|
462
|
-
// TODO potentially have a node customizable version for plain text
|
|
411
|
+
let clipboardEventTimeout = null;
|
|
463
412
|
|
|
413
|
+
// TODO custom selection
|
|
414
|
+
// TODO potentially have a node customizable version for plain text
|
|
464
415
|
/**
|
|
465
416
|
* Copies the content of the current selection to the clipboard in
|
|
466
417
|
* text/plain, text/html, and application/x-lexical-editor (Lexical JSON)
|
|
@@ -470,14 +421,12 @@ let clipboardEventTimeout = null; // TODO custom selection
|
|
|
470
421
|
* @param event the native browser ClipboardEvent to add the content to.
|
|
471
422
|
* @returns
|
|
472
423
|
*/
|
|
473
|
-
|
|
474
424
|
async function copyToClipboard(editor, event) {
|
|
475
425
|
if (clipboardEventTimeout !== null) {
|
|
476
426
|
// Prevent weird race conditions that can happen when this function is run multiple times
|
|
477
427
|
// synchronously. In the future, we can do better, we can cancel/override the previously running job.
|
|
478
428
|
return false;
|
|
479
429
|
}
|
|
480
|
-
|
|
481
430
|
if (event !== null) {
|
|
482
431
|
return new Promise((resolve, reject) => {
|
|
483
432
|
editor.update(() => {
|
|
@@ -485,15 +434,12 @@ async function copyToClipboard(editor, event) {
|
|
|
485
434
|
});
|
|
486
435
|
});
|
|
487
436
|
}
|
|
488
|
-
|
|
489
437
|
const rootElement = editor.getRootElement();
|
|
490
438
|
const windowDocument = editor._window == null ? window.document : editor._window.document;
|
|
491
439
|
const domSelection = getDOMSelection(editor._window);
|
|
492
|
-
|
|
493
440
|
if (rootElement === null || domSelection === null) {
|
|
494
441
|
return false;
|
|
495
442
|
}
|
|
496
|
-
|
|
497
443
|
const element = windowDocument.createElement('span');
|
|
498
444
|
element.style.cssText = 'position: fixed; top: -1000px;';
|
|
499
445
|
element.append(windowDocument.createTextNode('#'));
|
|
@@ -507,20 +453,17 @@ async function copyToClipboard(editor, event) {
|
|
|
507
453
|
const removeListener = editor.registerCommand(lexical.COPY_COMMAND, secondEvent => {
|
|
508
454
|
if (utils.objectKlassEquals(secondEvent, ClipboardEvent)) {
|
|
509
455
|
removeListener();
|
|
510
|
-
|
|
511
456
|
if (clipboardEventTimeout !== null) {
|
|
512
457
|
window.clearTimeout(clipboardEventTimeout);
|
|
513
458
|
clipboardEventTimeout = null;
|
|
514
459
|
}
|
|
515
|
-
|
|
516
460
|
resolve($copyToClipboardEvent(editor, secondEvent));
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
461
|
+
}
|
|
462
|
+
// Block the entire copy flow while we wait for the next ClipboardEvent
|
|
520
463
|
return true;
|
|
521
|
-
}, lexical.COMMAND_PRIORITY_CRITICAL);
|
|
464
|
+
}, lexical.COMMAND_PRIORITY_CRITICAL);
|
|
465
|
+
// If the above hack execCommand hack works, this timeout code should never fire. Otherwise,
|
|
522
466
|
// the listener will be quickly freed so that the user can reuse it again
|
|
523
|
-
|
|
524
467
|
clipboardEventTimeout = window.setTimeout(() => {
|
|
525
468
|
removeListener();
|
|
526
469
|
clipboardEventTimeout = null;
|
|
@@ -529,46 +472,37 @@ async function copyToClipboard(editor, event) {
|
|
|
529
472
|
windowDocument.execCommand('copy');
|
|
530
473
|
element.remove();
|
|
531
474
|
});
|
|
532
|
-
}
|
|
475
|
+
}
|
|
533
476
|
|
|
477
|
+
// TODO shouldn't pass editor (pass namespace directly)
|
|
534
478
|
function $copyToClipboardEvent(editor, event) {
|
|
535
479
|
const domSelection = getDOMSelection(editor._window);
|
|
536
|
-
|
|
537
480
|
if (!domSelection) {
|
|
538
481
|
return false;
|
|
539
482
|
}
|
|
540
|
-
|
|
541
483
|
const anchorDOM = domSelection.anchorNode;
|
|
542
484
|
const focusDOM = domSelection.focusNode;
|
|
543
|
-
|
|
544
485
|
if (anchorDOM !== null && focusDOM !== null && !lexical.isSelectionWithinEditor(editor, anchorDOM, focusDOM)) {
|
|
545
486
|
return false;
|
|
546
487
|
}
|
|
547
|
-
|
|
548
488
|
event.preventDefault();
|
|
549
489
|
const clipboardData = event.clipboardData;
|
|
550
490
|
const selection = lexical.$getSelection();
|
|
551
|
-
|
|
552
491
|
if (clipboardData === null || selection === null) {
|
|
553
492
|
return false;
|
|
554
493
|
}
|
|
555
|
-
|
|
556
494
|
const htmlString = $getHtmlContent(editor);
|
|
557
495
|
const lexicalString = $getLexicalContent(editor);
|
|
558
496
|
let plainString = '';
|
|
559
|
-
|
|
560
497
|
if (selection !== null) {
|
|
561
498
|
plainString = selection.getTextContent();
|
|
562
499
|
}
|
|
563
|
-
|
|
564
500
|
if (htmlString !== null) {
|
|
565
501
|
clipboardData.setData('text/html', htmlString);
|
|
566
502
|
}
|
|
567
|
-
|
|
568
503
|
if (lexicalString !== null) {
|
|
569
504
|
clipboardData.setData('application/x-lexical-editor', lexicalString);
|
|
570
505
|
}
|
|
571
|
-
|
|
572
506
|
clipboardData.setData('text/plain', plainString);
|
|
573
507
|
return true;
|
|
574
508
|
}
|
package/LexicalClipboard.prod.js
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
*/
|
|
7
7
|
'use strict';var d=require("@lexical/html"),p=require("@lexical/selection"),r=require("@lexical/utils"),u=require("lexical");function z(a){let b=new URLSearchParams;b.append("code",a);for(let c=1;c<arguments.length;c++)b.append("v",arguments[c]);throw Error(`Minified Lexical error #${a}; visit https://lexical.dev/docs/error?${b} for the full message or `+"use the non-minified dev environment for full errors and additional helpful warnings.");}
|
|
8
8
|
let A="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement;function B(a){let b=u.$getSelection();if(null==b)throw Error("Expected valid LexicalSelection");return u.$isRangeSelection(b)&&b.isCollapsed()||0===b.getNodes().length?"":d.$generateHtmlFromNodes(a,b)}
|
|
9
|
-
function C(a){let b=u.$getSelection();if(null==b)throw Error("Expected valid LexicalSelection");return u.$isRangeSelection(b)&&b.isCollapsed()||0===b.getNodes().length?null:JSON.stringify(D(a,b))}function E(a,b,c){(u.DEPRECATED_$isGridSelection(c)||null!==r.$findMatchingParent(c.anchor.getNode(),
|
|
10
|
-
function I(a,b){let c=[],
|
|
11
|
-
function F(a,b,c,
|
|
12
|
-
m=Math.min(b.getChildrenSize()-1,k+
|
|
13
|
-
w.getKey():l===k&&v===
|
|
14
|
-
function J(a,b,c,
|
|
15
|
-
m[
|
|
16
|
-
function M(a,b){var c=A?(a._window||window).getSelection():null;if(!c)return!1;var
|
|
9
|
+
function C(a){let b=u.$getSelection();if(null==b)throw Error("Expected valid LexicalSelection");return u.$isRangeSelection(b)&&b.isCollapsed()||0===b.getNodes().length?null:JSON.stringify(D(a,b))}function E(a,b,c){(u.DEPRECATED_$isGridSelection(c)||null!==r.$findMatchingParent(c.anchor.getNode(),f=>u.DEPRECATED_$isGridCellNode(f))&&null!==r.$findMatchingParent(c.focus.getNode(),f=>u.DEPRECATED_$isGridCellNode(f)))&&1===b.length&&u.DEPRECATED_$isGridNode(b[0])?F(b,c,!1,a):I(b,c)}
|
|
10
|
+
function I(a,b){let c=[],f=null;for(let e=0;e<a.length;e++){let g=a[e],h=u.$isLineBreakNode(g);if(h||u.$isDecoratorNode(g)&&g.isInline()||u.$isElementNode(g)&&g.isInline()||u.$isTextNode(g)||g.isParentRequired()){if(null===f&&(f=g.createParentElementNode(),c.push(f),h))continue;null!==f&&f.append(g)}else c.push(g),f=null}u.$isRangeSelection(b)?b.insertNodes(c):u.DEPRECATED_$isGridSelection(b)&&(a=b.anchor.getNode(),u.DEPRECATED_$isGridCellNode(a)||z(41),a.append(...c))}
|
|
11
|
+
function F(a,b,c,f){1===a.length&&u.DEPRECATED_$isGridNode(a[0])||z(42);var e=a[0];a=e.getChildren();c=e.getFirstChildOrThrow().getChildrenSize();var g=e.getChildrenSize(),h=r.$findMatchingParent(b.anchor.getNode(),l=>u.DEPRECATED_$isGridCellNode(l));b=(e=h&&r.$findMatchingParent(h,l=>u.DEPRECATED_$isGridRowNode(l)))&&r.$findMatchingParent(e,l=>u.DEPRECATED_$isGridNode(l));u.DEPRECATED_$isGridCellNode(h)&&u.DEPRECATED_$isGridRowNode(e)&&u.DEPRECATED_$isGridNode(b)||z(43);var k=e.getIndexWithinParent(),
|
|
12
|
+
m=Math.min(b.getChildrenSize()-1,k+g-1);g=h.getIndexWithinParent();h=Math.min(e.getChildrenSize()-1,g+c-1);c=Math.min(g,h);e=Math.min(k,m);g=Math.max(g,h);k=Math.max(k,m);m=b.getChildren();h=0;let n,q;for(let l=e;l<=k;l++){var t=m[l];u.DEPRECATED_$isGridRowNode(t)||z(24);var y=a[h];u.DEPRECATED_$isGridRowNode(y)||z(24);t=t.getChildren();y=y.getChildren();let G=0;for(let v=c;v<=g;v++){let w=t[v];u.DEPRECATED_$isGridCellNode(w)||z(25);let H=y[G];u.DEPRECATED_$isGridCellNode(H)||z(25);l===e&&v===c?n=
|
|
13
|
+
w.getKey():l===k&&v===g&&(q=w.getKey());let N=w.getChildren();H.getChildren().forEach(x=>{u.$isTextNode(x)&&u.$createParagraphNode().append(x);w.append(x)});N.forEach(x=>x.remove());G++}h++}n&&q&&(a=u.DEPRECATED_$createGridSelection(),a.set(b.getKey(),n,q),u.$setSelection(a),f.dispatchCommand(u.SELECTION_CHANGE_COMMAND,void 0))}
|
|
14
|
+
function J(a,b,c,f=[]){let e=null!=b?c.isSelected(b):!0,g=u.$isElementNode(c)&&c.excludeFromCopy("html");var h=c;if(null!==b){var k=p.$cloneWithProperties(c);h=k=u.$isTextNode(k)&&null!=b?p.$sliceSelectedTextNodeContent(b,k):k}let m=u.$isElementNode(h)?h.getChildren():[];var n=h;k=n.exportJSON();var q=n.constructor;k.type!==q.getType()&&z(58,q.name);let t=k.children;u.$isElementNode(n)&&(Array.isArray(t)||z(59,q.name));u.$isTextNode(h)&&(h=h.__text,0<h.length?k.text=h:e=!1);for(h=0;h<m.length;h++)n=
|
|
15
|
+
m[h],q=J(a,b,n,k.children),!e&&u.$isElementNode(c)&&q&&c.extractWithChild(n,b,"clone")&&(e=!0);if(e&&!g)f.push(k);else if(Array.isArray(k.children))for(a=0;a<k.children.length;a++)f.push(k.children[a]);return e}function D(a,b){let c=[],f=u.$getRoot().getChildren();for(let e=0;e<f.length;e++)J(a,b,f[e],c);return{namespace:a._config.namespace,nodes:c}}function K(a){let b=[];for(let c=0;c<a.length;c++){let f=u.$parseSerializedNode(a[c]);u.$isTextNode(f)&&p.$addNodeStyle(f);b.push(f)}return b}let L=null;
|
|
16
|
+
function M(a,b){var c=A?(a._window||window).getSelection():null;if(!c)return!1;var f=c.anchorNode;c=c.focusNode;if(null!==f&&null!==c&&!u.isSelectionWithinEditor(a,f,c))return!1;b.preventDefault();b=b.clipboardData;f=u.$getSelection();if(null===b||null===f)return!1;c=B(a);a=C(a);let e="";null!==f&&(e=f.getTextContent());null!==c&&b.setData("text/html",c);null!==a&&b.setData("application/x-lexical-editor",a);b.setData("text/plain",e);return!0}exports.$generateJSONFromSelectedNodes=D;
|
|
17
17
|
exports.$generateNodesFromSerializedNodes=K;exports.$getHtmlContent=B;exports.$getLexicalContent=C;exports.$insertDataTransferForPlainText=function(a,b){a=a.getData("text/plain")||a.getData("text/uri-list");null!=a&&b.insertRawText(a)};
|
|
18
|
-
exports.$insertDataTransferForRichText=function(a,b,c){var
|
|
19
|
-
|
|
20
|
-
exports.copyToClipboard=async function(a,b){if(null!==L)return!1;if(null!==b)return new Promise(
|
|
21
|
-
{let k=a.registerCommand(u.COPY_COMMAND,m=>{r.objectKlassEquals(m,ClipboardEvent)&&(k(),null!==L&&(window.clearTimeout(L),L=null),
|
|
18
|
+
exports.$insertDataTransferForRichText=function(a,b,c){var f=a.getData("application/x-lexical-editor");if(f)try{let g=JSON.parse(f);if(g.namespace===c._config.namespace&&Array.isArray(g.nodes)){let h=K(g.nodes);return E(c,h,b)}}catch(g){}if(f=a.getData("text/html"))try{var e=(new DOMParser).parseFromString(f,"text/html");let g=d.$generateNodesFromDOM(c,e);return E(c,g,b)}catch(g){}a=a.getData("text/plain")||a.getData("text/uri-list");if(null!=a)if(u.$isRangeSelection(b))for(a=a.split(/(\r?\n|\t)/),
|
|
19
|
+
""===a[a.length-1]&&a.pop(),c=0;c<a.length;c++)e=a[c],"\n"===e||"\r\n"===e?b.insertParagraph():"\t"===e?b.insertNodes([u.$createTabNode()]):b.insertText(e);else b.insertRawText(a)};exports.$insertGeneratedNodes=E;
|
|
20
|
+
exports.copyToClipboard=async function(a,b){if(null!==L)return!1;if(null!==b)return new Promise(h=>{a.update(()=>{h(M(a,b))})});var c=a.getRootElement();let f=null==a._window?window.document:a._window.document,e=A?(a._window||window).getSelection():null;if(null===c||null===e)return!1;let g=f.createElement("span");g.style.cssText="position: fixed; top: -1000px;";g.append(f.createTextNode("#"));c.append(g);c=new Range;c.setStart(g,0);c.setEnd(g,1);e.removeAllRanges();e.addRange(c);return new Promise(h=>
|
|
21
|
+
{let k=a.registerCommand(u.COPY_COMMAND,m=>{r.objectKlassEquals(m,ClipboardEvent)&&(k(),null!==L&&(window.clearTimeout(L),L=null),h(M(a,m)));return!0},u.COMMAND_PRIORITY_CRITICAL);L=window.setTimeout(()=>{k();L=null;h(!1)},50);f.execCommand("copy");g.remove()})}
|
package/package.json
CHANGED
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
"paste"
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"version": "0.12.
|
|
12
|
+
"version": "0.12.4",
|
|
13
13
|
"main": "LexicalClipboard.js",
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"lexical": "0.12.
|
|
15
|
+
"lexical": "0.12.4"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@lexical/utils": "0.12.
|
|
19
|
-
"@lexical/list": "0.12.
|
|
20
|
-
"@lexical/selection": "0.12.
|
|
21
|
-
"@lexical/html": "0.12.
|
|
18
|
+
"@lexical/utils": "0.12.4",
|
|
19
|
+
"@lexical/list": "0.12.4",
|
|
20
|
+
"@lexical/selection": "0.12.4",
|
|
21
|
+
"@lexical/html": "0.12.4"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|