@lexical/clipboard 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LexicalClipboard.dev.esm.js +380 -0
- package/LexicalClipboard.esm.js +17 -0
- package/LexicalClipboard.js +1 -1
- package/LexicalClipboard.prod.esm.js +7 -0
- package/README.md +2 -0
- package/package.json +9 -7
|
@@ -0,0 +1,380 @@
|
|
|
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
|
+
import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html';
|
|
8
|
+
import { $addNodeStyle, $cloneWithProperties, $sliceSelectedTextNodeContent } from '@lexical/selection';
|
|
9
|
+
import { objectKlassEquals } from '@lexical/utils';
|
|
10
|
+
import { $getSelection, $isRangeSelection, $createTabNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $getRoot, $parseSerializedNode, $isTextNode, COPY_COMMAND, COMMAND_PRIORITY_CRITICAL, isSelectionWithinEditor, $isElementNode } from 'lexical';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14
|
+
*
|
|
15
|
+
* This source code is licensed under the MIT license found in the
|
|
16
|
+
* LICENSE file in the root directory of this source tree.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
24
|
+
*
|
|
25
|
+
* This source code is licensed under the MIT license found in the
|
|
26
|
+
* LICENSE file in the root directory of this source tree.
|
|
27
|
+
*
|
|
28
|
+
*/
|
|
29
|
+
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the *currently selected* Lexical content as an HTML string, relying on the
|
|
33
|
+
* logic defined in the exportDOM methods on the LexicalNode classes. Note that
|
|
34
|
+
* this will not return the HTML content of the entire editor (unless all the content is included
|
|
35
|
+
* in the current selection).
|
|
36
|
+
*
|
|
37
|
+
* @param editor - LexicalEditor instance to get HTML content from
|
|
38
|
+
* @returns a string of HTML content
|
|
39
|
+
*/
|
|
40
|
+
function $getHtmlContent(editor) {
|
|
41
|
+
const selection = $getSelection();
|
|
42
|
+
if (selection == null) {
|
|
43
|
+
{
|
|
44
|
+
throw Error(`Expected valid LexicalSelection`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If we haven't selected anything
|
|
49
|
+
if ($isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
return $generateHtmlFromNodes(editor, selection);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the *currently selected* Lexical content as a JSON string, relying on the
|
|
57
|
+
* logic defined in the exportJSON methods on the LexicalNode classes. Note that
|
|
58
|
+
* this will not return the JSON content of the entire editor (unless all the content is included
|
|
59
|
+
* in the current selection).
|
|
60
|
+
*
|
|
61
|
+
* @param editor - LexicalEditor instance to get the JSON content from
|
|
62
|
+
* @returns
|
|
63
|
+
*/
|
|
64
|
+
function $getLexicalContent(editor) {
|
|
65
|
+
const selection = $getSelection();
|
|
66
|
+
if (selection == null) {
|
|
67
|
+
{
|
|
68
|
+
throw Error(`Expected valid LexicalSelection`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// If we haven't selected anything
|
|
73
|
+
if ($isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return JSON.stringify($generateJSONFromSelectedNodes(editor, selection));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Attempts to insert content of the mime-types text/plain or text/uri-list from
|
|
81
|
+
* the provided DataTransfer object into the editor at the provided selection.
|
|
82
|
+
* text/uri-list is only used if text/plain is not also provided.
|
|
83
|
+
*
|
|
84
|
+
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
|
|
85
|
+
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
|
|
86
|
+
*/
|
|
87
|
+
function $insertDataTransferForPlainText(dataTransfer, selection) {
|
|
88
|
+
const text = dataTransfer.getData('text/plain') || dataTransfer.getData('text/uri-list');
|
|
89
|
+
if (text != null) {
|
|
90
|
+
selection.insertRawText(text);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Attempts to insert content of the mime-types application/x-lexical-editor, text/html,
|
|
96
|
+
* text/plain, or text/uri-list (in descending order of priority) from the provided DataTransfer
|
|
97
|
+
* object into the editor at the provided selection.
|
|
98
|
+
*
|
|
99
|
+
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
|
|
100
|
+
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
|
|
101
|
+
* @param editor the LexicalEditor the content is being inserted into.
|
|
102
|
+
*/
|
|
103
|
+
function $insertDataTransferForRichText(dataTransfer, selection, editor) {
|
|
104
|
+
const lexicalString = dataTransfer.getData('application/x-lexical-editor');
|
|
105
|
+
if (lexicalString) {
|
|
106
|
+
try {
|
|
107
|
+
const payload = JSON.parse(lexicalString);
|
|
108
|
+
if (payload.namespace === editor._config.namespace && Array.isArray(payload.nodes)) {
|
|
109
|
+
const nodes = $generateNodesFromSerializedNodes(payload.nodes);
|
|
110
|
+
return $insertGeneratedNodes(editor, nodes, selection);
|
|
111
|
+
}
|
|
112
|
+
} catch (_unused) {
|
|
113
|
+
// Fail silently.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const htmlString = dataTransfer.getData('text/html');
|
|
117
|
+
if (htmlString) {
|
|
118
|
+
try {
|
|
119
|
+
const parser = new DOMParser();
|
|
120
|
+
const dom = parser.parseFromString(htmlString, 'text/html');
|
|
121
|
+
const nodes = $generateNodesFromDOM(editor, dom);
|
|
122
|
+
return $insertGeneratedNodes(editor, nodes, selection);
|
|
123
|
+
} catch (_unused2) {
|
|
124
|
+
// Fail silently.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Multi-line plain text in rich text mode pasted as separate paragraphs
|
|
129
|
+
// instead of single paragraph with linebreaks.
|
|
130
|
+
// Webkit-specific: Supports read 'text/uri-list' in clipboard.
|
|
131
|
+
const text = dataTransfer.getData('text/plain') || dataTransfer.getData('text/uri-list');
|
|
132
|
+
if (text != null) {
|
|
133
|
+
if ($isRangeSelection(selection)) {
|
|
134
|
+
const parts = text.split(/(\r?\n|\t)/);
|
|
135
|
+
if (parts[parts.length - 1] === '') {
|
|
136
|
+
parts.pop();
|
|
137
|
+
}
|
|
138
|
+
for (let i = 0; i < parts.length; i++) {
|
|
139
|
+
const part = parts[i];
|
|
140
|
+
if (part === '\n' || part === '\r\n') {
|
|
141
|
+
selection.insertParagraph();
|
|
142
|
+
} else if (part === '\t') {
|
|
143
|
+
selection.insertNodes([$createTabNode()]);
|
|
144
|
+
} else {
|
|
145
|
+
selection.insertText(part);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
selection.insertRawText(text);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Inserts Lexical nodes into the editor using different strategies depending on
|
|
156
|
+
* some simple selection-based heuristics. If you're looking for a generic way to
|
|
157
|
+
* to insert nodes into the editor at a specific selection point, you probably want
|
|
158
|
+
* {@link lexical.$insertNodes}
|
|
159
|
+
*
|
|
160
|
+
* @param editor LexicalEditor instance to insert the nodes into.
|
|
161
|
+
* @param nodes The nodes to insert.
|
|
162
|
+
* @param selection The selection to insert the nodes into.
|
|
163
|
+
*/
|
|
164
|
+
function $insertGeneratedNodes(editor, nodes, selection) {
|
|
165
|
+
if (!editor.dispatchCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, {
|
|
166
|
+
nodes,
|
|
167
|
+
selection
|
|
168
|
+
})) {
|
|
169
|
+
selection.insertNodes(nodes);
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
function exportNodeToJSON(node) {
|
|
174
|
+
const serializedNode = node.exportJSON();
|
|
175
|
+
const nodeClass = node.constructor;
|
|
176
|
+
if (serializedNode.type !== nodeClass.getType()) {
|
|
177
|
+
{
|
|
178
|
+
throw Error(`LexicalNode: Node ${nodeClass.name} does not implement .exportJSON().`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if ($isElementNode(node)) {
|
|
182
|
+
const serializedChildren = serializedNode.children;
|
|
183
|
+
if (!Array.isArray(serializedChildren)) {
|
|
184
|
+
{
|
|
185
|
+
throw Error(`LexicalNode: Node ${nodeClass.name} is an element but .exportJSON() does not have a children array.`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return serializedNode;
|
|
190
|
+
}
|
|
191
|
+
function $appendNodesToJSON(editor, selection, currentNode, targetArray = []) {
|
|
192
|
+
let shouldInclude = selection !== null ? currentNode.isSelected(selection) : true;
|
|
193
|
+
const shouldExclude = $isElementNode(currentNode) && currentNode.excludeFromCopy('html');
|
|
194
|
+
let target = currentNode;
|
|
195
|
+
if (selection !== null) {
|
|
196
|
+
let clone = $cloneWithProperties(currentNode);
|
|
197
|
+
clone = $isTextNode(clone) && selection !== null ? $sliceSelectedTextNodeContent(selection, clone) : clone;
|
|
198
|
+
target = clone;
|
|
199
|
+
}
|
|
200
|
+
const children = $isElementNode(target) ? target.getChildren() : [];
|
|
201
|
+
const serializedNode = exportNodeToJSON(target);
|
|
202
|
+
|
|
203
|
+
// TODO: TextNode calls getTextContent() (NOT node.__text) within it's exportJSON method
|
|
204
|
+
// which uses getLatest() to get the text from the original node with the same key.
|
|
205
|
+
// This is a deeper issue with the word "clone" here, it's still a reference to the
|
|
206
|
+
// same node as far as the LexicalEditor is concerned since it shares a key.
|
|
207
|
+
// We need a way to create a clone of a Node in memory with it's own key, but
|
|
208
|
+
// until then this hack will work for the selected text extract use case.
|
|
209
|
+
if ($isTextNode(target)) {
|
|
210
|
+
const text = target.__text;
|
|
211
|
+
// If an uncollapsed selection ends or starts at the end of a line of specialized,
|
|
212
|
+
// TextNodes, such as code tokens, we will get a 'blank' TextNode here, i.e., one
|
|
213
|
+
// with text of length 0. We don't want this, it makes a confusing mess. Reset!
|
|
214
|
+
if (text.length > 0) {
|
|
215
|
+
serializedNode.text = text;
|
|
216
|
+
} else {
|
|
217
|
+
shouldInclude = false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (let i = 0; i < children.length; i++) {
|
|
221
|
+
const childNode = children[i];
|
|
222
|
+
const shouldIncludeChild = $appendNodesToJSON(editor, selection, childNode, serializedNode.children);
|
|
223
|
+
if (!shouldInclude && $isElementNode(currentNode) && shouldIncludeChild && currentNode.extractWithChild(childNode, selection, 'clone')) {
|
|
224
|
+
shouldInclude = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (shouldInclude && !shouldExclude) {
|
|
228
|
+
targetArray.push(serializedNode);
|
|
229
|
+
} else if (Array.isArray(serializedNode.children)) {
|
|
230
|
+
for (let i = 0; i < serializedNode.children.length; i++) {
|
|
231
|
+
const serializedChildNode = serializedNode.children[i];
|
|
232
|
+
targetArray.push(serializedChildNode);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return shouldInclude;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// TODO why $ function with Editor instance?
|
|
239
|
+
/**
|
|
240
|
+
* Gets the Lexical JSON of the nodes inside the provided Selection.
|
|
241
|
+
*
|
|
242
|
+
* @param editor LexicalEditor to get the JSON content from.
|
|
243
|
+
* @param selection Selection to get the JSON content from.
|
|
244
|
+
* @returns an object with the editor namespace and a list of serializable nodes as JavaScript objects.
|
|
245
|
+
*/
|
|
246
|
+
function $generateJSONFromSelectedNodes(editor, selection) {
|
|
247
|
+
const nodes = [];
|
|
248
|
+
const root = $getRoot();
|
|
249
|
+
const topLevelChildren = root.getChildren();
|
|
250
|
+
for (let i = 0; i < topLevelChildren.length; i++) {
|
|
251
|
+
const topLevelNode = topLevelChildren[i];
|
|
252
|
+
$appendNodesToJSON(editor, selection, topLevelNode, nodes);
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
namespace: editor._config.namespace,
|
|
256
|
+
nodes
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* This method takes an array of objects conforming to the BaseSeralizedNode interface and returns
|
|
262
|
+
* an Array containing instances of the corresponding LexicalNode classes registered on the editor.
|
|
263
|
+
* Normally, you'd get an Array of BaseSerialized nodes from {@link $generateJSONFromSelectedNodes}
|
|
264
|
+
*
|
|
265
|
+
* @param serializedNodes an Array of objects conforming to the BaseSerializedNode interface.
|
|
266
|
+
* @returns an Array of Lexical Node objects.
|
|
267
|
+
*/
|
|
268
|
+
function $generateNodesFromSerializedNodes(serializedNodes) {
|
|
269
|
+
const nodes = [];
|
|
270
|
+
for (let i = 0; i < serializedNodes.length; i++) {
|
|
271
|
+
const serializedNode = serializedNodes[i];
|
|
272
|
+
const node = $parseSerializedNode(serializedNode);
|
|
273
|
+
if ($isTextNode(node)) {
|
|
274
|
+
$addNodeStyle(node);
|
|
275
|
+
}
|
|
276
|
+
nodes.push(node);
|
|
277
|
+
}
|
|
278
|
+
return nodes;
|
|
279
|
+
}
|
|
280
|
+
const EVENT_LATENCY = 50;
|
|
281
|
+
let clipboardEventTimeout = null;
|
|
282
|
+
|
|
283
|
+
// TODO custom selection
|
|
284
|
+
// TODO potentially have a node customizable version for plain text
|
|
285
|
+
/**
|
|
286
|
+
* Copies the content of the current selection to the clipboard in
|
|
287
|
+
* text/plain, text/html, and application/x-lexical-editor (Lexical JSON)
|
|
288
|
+
* formats.
|
|
289
|
+
*
|
|
290
|
+
* @param editor the LexicalEditor instance to copy content from
|
|
291
|
+
* @param event the native browser ClipboardEvent to add the content to.
|
|
292
|
+
* @returns
|
|
293
|
+
*/
|
|
294
|
+
async function copyToClipboard(editor, event) {
|
|
295
|
+
if (clipboardEventTimeout !== null) {
|
|
296
|
+
// Prevent weird race conditions that can happen when this function is run multiple times
|
|
297
|
+
// synchronously. In the future, we can do better, we can cancel/override the previously running job.
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
if (event !== null) {
|
|
301
|
+
return new Promise((resolve, reject) => {
|
|
302
|
+
editor.update(() => {
|
|
303
|
+
resolve($copyToClipboardEvent(editor, event));
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const rootElement = editor.getRootElement();
|
|
308
|
+
const windowDocument = editor._window == null ? window.document : editor._window.document;
|
|
309
|
+
const domSelection = getDOMSelection(editor._window);
|
|
310
|
+
if (rootElement === null || domSelection === null) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
const element = windowDocument.createElement('span');
|
|
314
|
+
element.style.cssText = 'position: fixed; top: -1000px;';
|
|
315
|
+
element.append(windowDocument.createTextNode('#'));
|
|
316
|
+
rootElement.append(element);
|
|
317
|
+
const range = new Range();
|
|
318
|
+
range.setStart(element, 0);
|
|
319
|
+
range.setEnd(element, 1);
|
|
320
|
+
domSelection.removeAllRanges();
|
|
321
|
+
domSelection.addRange(range);
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
const removeListener = editor.registerCommand(COPY_COMMAND, secondEvent => {
|
|
324
|
+
if (objectKlassEquals(secondEvent, ClipboardEvent)) {
|
|
325
|
+
removeListener();
|
|
326
|
+
if (clipboardEventTimeout !== null) {
|
|
327
|
+
window.clearTimeout(clipboardEventTimeout);
|
|
328
|
+
clipboardEventTimeout = null;
|
|
329
|
+
}
|
|
330
|
+
resolve($copyToClipboardEvent(editor, secondEvent));
|
|
331
|
+
}
|
|
332
|
+
// Block the entire copy flow while we wait for the next ClipboardEvent
|
|
333
|
+
return true;
|
|
334
|
+
}, COMMAND_PRIORITY_CRITICAL);
|
|
335
|
+
// If the above hack execCommand hack works, this timeout code should never fire. Otherwise,
|
|
336
|
+
// the listener will be quickly freed so that the user can reuse it again
|
|
337
|
+
clipboardEventTimeout = window.setTimeout(() => {
|
|
338
|
+
removeListener();
|
|
339
|
+
clipboardEventTimeout = null;
|
|
340
|
+
resolve(false);
|
|
341
|
+
}, EVENT_LATENCY);
|
|
342
|
+
windowDocument.execCommand('copy');
|
|
343
|
+
element.remove();
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// TODO shouldn't pass editor (pass namespace directly)
|
|
348
|
+
function $copyToClipboardEvent(editor, event) {
|
|
349
|
+
const domSelection = getDOMSelection(editor._window);
|
|
350
|
+
if (!domSelection) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
const anchorDOM = domSelection.anchorNode;
|
|
354
|
+
const focusDOM = domSelection.focusNode;
|
|
355
|
+
if (anchorDOM !== null && focusDOM !== null && !isSelectionWithinEditor(editor, anchorDOM, focusDOM)) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
const clipboardData = event.clipboardData;
|
|
360
|
+
const selection = $getSelection();
|
|
361
|
+
if (clipboardData === null || selection === null) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
const htmlString = $getHtmlContent(editor);
|
|
365
|
+
const lexicalString = $getLexicalContent(editor);
|
|
366
|
+
let plainString = '';
|
|
367
|
+
if (selection !== null) {
|
|
368
|
+
plainString = selection.getTextContent();
|
|
369
|
+
}
|
|
370
|
+
if (htmlString !== null) {
|
|
371
|
+
clipboardData.setData('text/html', htmlString);
|
|
372
|
+
}
|
|
373
|
+
if (lexicalString !== null) {
|
|
374
|
+
clipboardData.setData('application/x-lexical-editor', lexicalString);
|
|
375
|
+
}
|
|
376
|
+
clipboardData.setData('text/plain', plainString);
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export { $generateJSONFromSelectedNodes, $generateNodesFromSerializedNodes, $getHtmlContent, $getLexicalContent, $insertDataTransferForPlainText, $insertDataTransferForRichText, $insertGeneratedNodes, copyToClipboard };
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
import * as modDev from './LexicalClipboard.dev.esm.js';
|
|
8
|
+
import * as modProd from './LexicalClipboard.prod.esm.js';
|
|
9
|
+
const mod = process.env.NODE_ENV === 'development' ? modDev : modProd;
|
|
10
|
+
export const $generateJSONFromSelectedNodes = mod.$generateJSONFromSelectedNodes;
|
|
11
|
+
export const $generateNodesFromSerializedNodes = mod.$generateNodesFromSerializedNodes;
|
|
12
|
+
export const $getHtmlContent = mod.$getHtmlContent;
|
|
13
|
+
export const $getLexicalContent = mod.$getLexicalContent;
|
|
14
|
+
export const $insertDataTransferForPlainText = mod.$insertDataTransferForPlainText;
|
|
15
|
+
export const $insertDataTransferForRichText = mod.$insertDataTransferForRichText;
|
|
16
|
+
export const $insertGeneratedNodes = mod.$insertGeneratedNodes;
|
|
17
|
+
export const copyToClipboard = mod.copyToClipboard;
|
package/LexicalClipboard.js
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
'use strict'
|
|
8
|
-
const LexicalClipboard = process.env.NODE_ENV === 'development' ? require('./LexicalClipboard.dev.js') : require('./LexicalClipboard.prod.js')
|
|
8
|
+
const LexicalClipboard = process.env.NODE_ENV === 'development' ? require('./LexicalClipboard.dev.js') : require('./LexicalClipboard.prod.js');
|
|
9
9
|
module.exports = LexicalClipboard;
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
import{$generateHtmlFromNodes as e,$generateNodesFromDOM as t}from"@lexical/html";import{$addNodeStyle as n,$cloneWithProperties as o,$sliceSelectedTextNodeContent as l}from"@lexical/selection";import{objectKlassEquals as r}from"@lexical/utils";import{$getSelection as i,$isRangeSelection as a,$createTabNode as c,SELECTION_INSERT_CLIPBOARD_NODES_COMMAND as s,$getRoot as u,$parseSerializedNode as d,$isTextNode as f,COPY_COMMAND as p,COMMAND_PRIORITY_CRITICAL as m,isSelectionWithinEditor as h,$isElementNode as x}from"lexical";var g=function(e){const t=new URLSearchParams;t.append("code",e);for(let e=1;e<arguments.length;e++)t.append("v",arguments[e]);throw Error(`Minified Lexical error #${e}; visit https://lexical.dev/docs/error?${t} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)};const w="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,y=e=>w?(e||window).getSelection():null;function v(t){const n=i();if(null==n)throw Error("Expected valid LexicalSelection");return a(n)&&n.isCollapsed()||0===n.getNodes().length?"":e(t,n)}function D(e){const t=i();if(null==t)throw Error("Expected valid LexicalSelection");return a(t)&&t.isCollapsed()||0===t.getNodes().length?null:JSON.stringify(T(e,t))}function C(e,t){const n=e.getData("text/plain")||e.getData("text/uri-list");null!=n&&t.insertRawText(n)}function E(e,n,o){const l=e.getData("application/x-lexical-editor");if(l)try{const e=JSON.parse(l);if(e.namespace===o._config.namespace&&Array.isArray(e.nodes)){return N(o,_(e.nodes),n)}}catch(e){}const r=e.getData("text/html");if(r)try{const e=(new DOMParser).parseFromString(r,"text/html");return N(o,t(o,e),n)}catch(e){}const i=e.getData("text/plain")||e.getData("text/uri-list");if(null!=i)if(a(n)){const e=i.split(/(\r?\n|\t)/);""===e[e.length-1]&&e.pop();for(let t=0;t<e.length;t++){const o=e[t];"\n"===o||"\r\n"===o?n.insertParagraph():"\t"===o?n.insertNodes([c()]):n.insertText(o)}}else n.insertRawText(i)}function N(e,t,n){e.dispatchCommand(s,{nodes:t,selection:n})||n.insertNodes(t)}function S(e,t,n,r=[]){let i=null===t||n.isSelected(t);const a=x(n)&&n.excludeFromCopy("html");let c=n;if(null!==t){let e=o(n);e=f(e)&&null!==t?l(t,e):e,c=e}const s=x(c)?c.getChildren():[],u=function(e){const t=e.exportJSON(),n=e.constructor;if(t.type!==n.getType()&&g(58,n.name),x(e)){const e=t.children;Array.isArray(e)||g(59,n.name)}return t}(c);if(f(c)){const e=c.__text;e.length>0?u.text=e:i=!1}for(let o=0;o<s.length;o++){const l=s[o],r=S(e,t,l,u.children);!i&&x(n)&&r&&n.extractWithChild(l,t,"clone")&&(i=!0)}if(i&&!a)r.push(u);else if(Array.isArray(u.children))for(let e=0;e<u.children.length;e++){const t=u.children[e];r.push(t)}return i}function T(e,t){const n=[],o=u().getChildren();for(let l=0;l<o.length;l++){S(e,t,o[l],n)}return{namespace:e._config.namespace,nodes:n}}function _(e){const t=[];for(let o=0;o<e.length;o++){const l=e[o],r=d(l);f(r)&&n(r),t.push(r)}return t}let A=null;async function R(e,t){if(null!==A)return!1;if(null!==t)return new Promise(((n,o)=>{e.update((()=>{n(P(e,t))}))}));const n=e.getRootElement(),o=null==e._window?window.document:e._window.document,l=y(e._window);if(null===n||null===l)return!1;const i=o.createElement("span");i.style.cssText="position: fixed; top: -1000px;",i.append(o.createTextNode("#")),n.append(i);const a=new Range;return a.setStart(i,0),a.setEnd(i,1),l.removeAllRanges(),l.addRange(a),new Promise(((t,n)=>{const l=e.registerCommand(p,(n=>(r(n,ClipboardEvent)&&(l(),null!==A&&(window.clearTimeout(A),A=null),t(P(e,n))),!0)),m);A=window.setTimeout((()=>{l(),A=null,t(!1)}),50),o.execCommand("copy"),i.remove()}))}function P(e,t){const n=y(e._window);if(!n)return!1;const o=n.anchorNode,l=n.focusNode;if(null!==o&&null!==l&&!h(e,o,l))return!1;t.preventDefault();const r=t.clipboardData,a=i();if(null===r||null===a)return!1;const c=v(e),s=D(e);let u="";return null!==a&&(u=a.getTextContent()),null!==c&&r.setData("text/html",c),null!==s&&r.setData("application/x-lexical-editor",s),r.setData("text/plain",u),!0}export{T as $generateJSONFromSelectedNodes,_ as $generateNodesFromSerializedNodes,v as $getHtmlContent,D as $getLexicalContent,C as $insertDataTransferForPlainText,E as $insertDataTransferForRichText,N as $insertGeneratedNodes,R as copyToClipboard};
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -9,20 +9,22 @@
|
|
|
9
9
|
"paste"
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.14.0",
|
|
13
13
|
"main": "LexicalClipboard.js",
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"lexical": "0.
|
|
15
|
+
"lexical": "0.14.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@lexical/utils": "0.
|
|
19
|
-
"@lexical/list": "0.
|
|
20
|
-
"@lexical/selection": "0.
|
|
21
|
-
"@lexical/html": "0.
|
|
18
|
+
"@lexical/utils": "0.14.0",
|
|
19
|
+
"@lexical/list": "0.14.0",
|
|
20
|
+
"@lexical/selection": "0.14.0",
|
|
21
|
+
"@lexical/html": "0.14.0"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
25
|
"url": "https://github.com/facebook/lexical",
|
|
26
26
|
"directory": "packages/lexical-clipboard"
|
|
27
|
-
}
|
|
27
|
+
},
|
|
28
|
+
"module": "LexicalClipboard.esm.js",
|
|
29
|
+
"sideEffects": false
|
|
28
30
|
}
|