deploy: v0.12 fix 存档key名

This commit is contained in:
2026-06-24 03:09:02 +00:00
parent 7a08d49fe3
commit e0a5281119
58743 changed files with 7297269 additions and 39505 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+473
View File
@@ -0,0 +1,473 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict';
var html = require('@lexical/html');
var selection = require('@lexical/selection');
var utils = require('@lexical/utils');
var lexical = require('lexical');
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
/**
* Returns the *currently selected* Lexical content as an HTML string, relying on the
* logic defined in the exportDOM methods on the LexicalNode classes. Note that
* this will not return the HTML content of the entire editor (unless all the content is included
* in the current selection).
*
* @param editor - LexicalEditor instance to get HTML content from
* @param selection - The selection to use (default is $getSelection())
* @returns a string of HTML content
*/
function $getHtmlContent(editor, selection = lexical.$getSelection()) {
if (selection == null) {
{
formatDevErrorMessage(`Expected valid LexicalSelection`);
}
}
// If we haven't selected anything
if (lexical.$isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
return '';
}
return html.$generateHtmlFromNodes(editor, selection);
}
/**
* Returns the *currently selected* Lexical content as a JSON string, relying on the
* logic defined in the exportJSON methods on the LexicalNode classes. Note that
* this will not return the JSON content of the entire editor (unless all the content is included
* in the current selection).
*
* @param editor - LexicalEditor instance to get the JSON content from
* @param selection - The selection to use (default is $getSelection())
* @returns
*/
function $getLexicalContent(editor, selection = lexical.$getSelection()) {
if (selection == null) {
{
formatDevErrorMessage(`Expected valid LexicalSelection`);
}
}
// If we haven't selected anything
if (lexical.$isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
return null;
}
return JSON.stringify($generateJSONFromSelectedNodes(editor, selection));
}
/**
* Attempts to insert content of the mime-types text/plain or text/uri-list from
* the provided DataTransfer object into the editor at the provided selection.
* text/uri-list is only used if text/plain is not also provided.
*
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
*/
function $insertDataTransferForPlainText(dataTransfer, selection) {
const text = dataTransfer.getData('text/plain') || dataTransfer.getData('text/uri-list');
if (text != null) {
selection.insertRawText(text);
}
}
/**
* Attempts to insert content of the mime-types application/x-lexical-editor, text/html,
* text/plain, or text/uri-list (in descending order of priority) from the provided DataTransfer
* object into the editor at the provided selection.
*
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
* @param editor the LexicalEditor the content is being inserted into.
*/
function $insertDataTransferForRichText(dataTransfer, selection, editor) {
const lexicalString = dataTransfer.getData('application/x-lexical-editor');
if (lexicalString) {
try {
const payload = JSON.parse(lexicalString);
if (payload.namespace === editor._config.namespace && Array.isArray(payload.nodes)) {
const nodes = $generateNodesFromSerializedNodes(payload.nodes);
return $insertGeneratedNodes(editor, nodes, selection);
}
} catch (_unused) {
// Fail silently.
}
}
const htmlString = dataTransfer.getData('text/html');
const plainString = dataTransfer.getData('text/plain');
// Skip HTML handling if it matches the plain text representation.
// This avoids unnecessary processing for plain text strings created by
// iOS Safari autocorrect, which incorrectly includes a `text/html` type.
if (htmlString && plainString !== htmlString) {
try {
const parser = new DOMParser();
const dom = parser.parseFromString(trustHTML(htmlString), 'text/html');
const nodes = html.$generateNodesFromDOM(editor, dom);
return $insertGeneratedNodes(editor, nodes, selection);
} catch (_unused2) {
// Fail silently.
}
}
// Multi-line plain text in rich text mode pasted as separate paragraphs
// instead of single paragraph with linebreaks.
// Webkit-specific: Supports read 'text/uri-list' in clipboard.
const text = plainString || dataTransfer.getData('text/uri-list');
if (text != null) {
if (lexical.$isRangeSelection(selection)) {
const parts = text.split(/(\r?\n|\t)/);
if (parts[parts.length - 1] === '') {
parts.pop();
}
for (let i = 0; i < parts.length; i++) {
const currentSelection = lexical.$getSelection();
if (lexical.$isRangeSelection(currentSelection)) {
const part = parts[i];
if (part === '\n' || part === '\r\n') {
currentSelection.insertParagraph();
} else if (part === '\t') {
currentSelection.insertNodes([lexical.$createTabNode()]);
} else {
currentSelection.insertText(part);
}
}
}
} else {
selection.insertRawText(text);
}
}
}
function trustHTML(html) {
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const policy = window.trustedTypes.createPolicy('lexical', {
createHTML: input => input
});
return policy.createHTML(html);
}
return html;
}
/**
* Inserts Lexical nodes into the editor using different strategies depending on
* some simple selection-based heuristics. If you're looking for a generic way to
* to insert nodes into the editor at a specific selection point, you probably want
* {@link lexical.$insertNodes}
*
* @param editor LexicalEditor instance to insert the nodes into.
* @param nodes The nodes to insert.
* @param selection The selection to insert the nodes into.
*/
function $insertGeneratedNodes(editor, nodes, selection) {
if (!editor.dispatchCommand(lexical.SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, {
nodes,
selection
})) {
selection.insertNodes(nodes);
$updateSelectionOnInsert(selection);
}
return;
}
function $updateSelectionOnInsert(selection) {
if (lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
const anchor = selection.anchor;
let nodeToInspect = null;
const anchorCaret = lexical.$caretFromPoint(anchor, 'previous');
if (anchorCaret) {
if (lexical.$isTextPointCaret(anchorCaret)) {
nodeToInspect = anchorCaret.origin;
} else {
const range = lexical.$getCaretRange(anchorCaret, lexical.$getChildCaret(lexical.$getRoot(), 'next').getFlipped());
for (const caret of range) {
if (lexical.$isTextNode(caret.origin)) {
nodeToInspect = caret.origin;
break;
} else if (lexical.$isElementNode(caret.origin) && !caret.origin.isInline()) {
break;
}
}
}
}
if (nodeToInspect && lexical.$isTextNode(nodeToInspect)) {
const newFormat = nodeToInspect.getFormat();
const newStyle = nodeToInspect.getStyle();
if (selection.format !== newFormat || selection.style !== newStyle) {
selection.format = newFormat;
selection.style = newStyle;
selection.dirty = true;
}
}
}
}
function exportNodeToJSON(node) {
const serializedNode = node.exportJSON();
const nodeClass = node.constructor;
if (serializedNode.type !== nodeClass.getType()) {
{
formatDevErrorMessage(`LexicalNode: Node ${nodeClass.name} does not implement .exportJSON().`);
}
}
if (lexical.$isElementNode(node)) {
const serializedChildren = serializedNode.children;
if (!Array.isArray(serializedChildren)) {
{
formatDevErrorMessage(`LexicalNode: Node ${nodeClass.name} is an element but .exportJSON() does not have a children array.`);
}
}
}
return serializedNode;
}
function $appendNodesToJSON(editor, selection$1, currentNode, targetArray = []) {
let shouldInclude = selection$1 !== null ? currentNode.isSelected(selection$1) : true;
const shouldExclude = lexical.$isElementNode(currentNode) && currentNode.excludeFromCopy('html');
let target = currentNode;
if (selection$1 !== null) {
let clone = lexical.$cloneWithProperties(currentNode);
clone = lexical.$isTextNode(clone) && selection$1 !== null ? selection.$sliceSelectedTextNodeContent(selection$1, clone) : clone;
target = clone;
}
const children = lexical.$isElementNode(target) ? target.getChildren() : [];
const serializedNode = exportNodeToJSON(target);
// TODO: TextNode calls getTextContent() (NOT node.__text) within its exportJSON method
// which uses getLatest() to get the text from the original node with the same key.
// This is a deeper issue with the word "clone" here, it's still a reference to the
// same node as far as the LexicalEditor is concerned since it shares a key.
// We need a way to create a clone of a Node in memory with its own key, but
// until then this hack will work for the selected text extract use case.
if (lexical.$isTextNode(target)) {
const text = target.__text;
// If an uncollapsed selection ends or starts at the end of a line of specialized,
// TextNodes, such as code tokens, we will get a 'blank' TextNode here, i.e., one
// with text of length 0. We don't want this, it makes a confusing mess. Reset!
if (text.length > 0) {
serializedNode.text = text;
} else {
shouldInclude = false;
}
}
for (let i = 0; i < children.length; i++) {
const childNode = children[i];
const shouldIncludeChild = $appendNodesToJSON(editor, selection$1, childNode, serializedNode.children);
if (!shouldInclude && lexical.$isElementNode(currentNode) && shouldIncludeChild && currentNode.extractWithChild(childNode, selection$1, 'clone')) {
shouldInclude = true;
}
}
if (shouldInclude && !shouldExclude) {
targetArray.push(serializedNode);
} else if (Array.isArray(serializedNode.children)) {
for (let i = 0; i < serializedNode.children.length; i++) {
const serializedChildNode = serializedNode.children[i];
targetArray.push(serializedChildNode);
}
}
return shouldInclude;
}
// TODO why $ function with Editor instance?
/**
* Gets the Lexical JSON of the nodes inside the provided Selection.
*
* @param editor LexicalEditor to get the JSON content from.
* @param selection Selection to get the JSON content from.
* @returns an object with the editor namespace and a list of serializable nodes as JavaScript objects.
*/
function $generateJSONFromSelectedNodes(editor, selection) {
const nodes = [];
const root = lexical.$getRoot();
const topLevelChildren = root.getChildren();
for (let i = 0; i < topLevelChildren.length; i++) {
const topLevelNode = topLevelChildren[i];
$appendNodesToJSON(editor, selection, topLevelNode, nodes);
}
return {
namespace: editor._config.namespace,
nodes
};
}
/**
* This method takes an array of objects conforming to the BaseSerializedNode interface and returns
* an Array containing instances of the corresponding LexicalNode classes registered on the editor.
* Normally, you'd get an Array of BaseSerialized nodes from {@link $generateJSONFromSelectedNodes}
*
* @param serializedNodes an Array of objects conforming to the BaseSerializedNode interface.
* @returns an Array of Lexical Node objects.
*/
function $generateNodesFromSerializedNodes(serializedNodes) {
const nodes = [];
for (let i = 0; i < serializedNodes.length; i++) {
const serializedNode = serializedNodes[i];
const node = lexical.$parseSerializedNode(serializedNode);
if (lexical.$isTextNode(node)) {
selection.$addNodeStyle(node);
}
nodes.push(node);
}
return nodes;
}
const EVENT_LATENCY = 50;
let clipboardEventTimeout = null;
// TODO custom selection
// TODO potentially have a node customizable version for plain text
/**
* Copies the content of the current selection to the clipboard in
* text/plain, text/html, and application/x-lexical-editor (Lexical JSON)
* formats.
*
* @param editor the LexicalEditor instance to copy content from
* @param event the native browser ClipboardEvent to add the content to.
* @returns
*/
async function copyToClipboard(editor, event, data) {
if (clipboardEventTimeout !== null) {
// Prevent weird race conditions that can happen when this function is run multiple times
// synchronously. In the future, we can do better, we can cancel/override the previously running job.
return false;
}
if (event !== null) {
return new Promise((resolve, reject) => {
editor.update(() => {
resolve($copyToClipboardEvent(editor, event, data));
});
});
}
const rootElement = editor.getRootElement();
const editorWindow = editor._window || window;
const windowDocument = window.document;
const domSelection = lexical.getDOMSelection(editorWindow);
if (rootElement === null || domSelection === null) {
return false;
}
const element = windowDocument.createElement('span');
element.style.cssText = 'position: fixed; top: -1000px;';
element.append(windowDocument.createTextNode('#'));
rootElement.append(element);
const range = new Range();
range.setStart(element, 0);
range.setEnd(element, 1);
domSelection.removeAllRanges();
domSelection.addRange(range);
return new Promise((resolve, reject) => {
const removeListener = editor.registerCommand(lexical.COPY_COMMAND, secondEvent => {
if (utils.objectKlassEquals(secondEvent, ClipboardEvent)) {
removeListener();
if (clipboardEventTimeout !== null) {
window.clearTimeout(clipboardEventTimeout);
clipboardEventTimeout = null;
}
resolve($copyToClipboardEvent(editor, secondEvent, data));
}
// Block the entire copy flow while we wait for the next ClipboardEvent
return true;
}, lexical.COMMAND_PRIORITY_CRITICAL);
// If the above hack execCommand hack works, this timeout code should never fire. Otherwise,
// the listener will be quickly freed so that the user can reuse it again
clipboardEventTimeout = window.setTimeout(() => {
removeListener();
clipboardEventTimeout = null;
resolve(false);
}, EVENT_LATENCY);
windowDocument.execCommand('copy');
element.remove();
});
}
// TODO shouldn't pass editor (pass namespace directly)
function $copyToClipboardEvent(editor, event, data) {
if (data === undefined) {
const domSelection = lexical.getDOMSelection(editor._window);
if (!domSelection) {
return false;
}
const anchorDOM = domSelection.anchorNode;
const focusDOM = domSelection.focusNode;
if (anchorDOM !== null && focusDOM !== null && !lexical.isSelectionWithinEditor(editor, anchorDOM, focusDOM)) {
return false;
}
const selection = lexical.$getSelection();
if (selection === null) {
return false;
}
data = $getClipboardDataFromSelection(selection);
}
event.preventDefault();
const clipboardData = event.clipboardData;
if (clipboardData === null) {
return false;
}
setLexicalClipboardDataTransfer(clipboardData, data);
return true;
}
const clipboardDataFunctions = [['text/html', $getHtmlContent], ['application/x-lexical-editor', $getLexicalContent]];
/**
* Serialize the content of the current selection to strings in
* text/plain, text/html, and application/x-lexical-editor (Lexical JSON)
* formats (as available).
*
* @param selection the selection to serialize (defaults to $getSelection())
* @returns LexicalClipboardData
*/
function $getClipboardDataFromSelection(selection = lexical.$getSelection()) {
const clipboardData = {
'text/plain': selection ? selection.getTextContent() : ''
};
if (selection) {
const editor = lexical.$getEditor();
for (const [mimeType, $editorFn] of clipboardDataFunctions) {
const v = $editorFn(editor, selection);
if (v !== null) {
clipboardData[mimeType] = v;
}
}
}
return clipboardData;
}
/**
* Call setData on the given clipboardData for each MIME type present
* in the given data (from {@link $getClipboardDataFromSelection})
*
* @param clipboardData the event.clipboardData to populate from data
* @param data The lexical data
*/
function setLexicalClipboardDataTransfer(clipboardData, data) {
for (const k in data) {
const v = data[k];
if (v !== undefined) {
clipboardData.setData(k, v);
}
}
}
exports.$generateJSONFromSelectedNodes = $generateJSONFromSelectedNodes;
exports.$generateNodesFromSerializedNodes = $generateNodesFromSerializedNodes;
exports.$getClipboardDataFromSelection = $getClipboardDataFromSelection;
exports.$getHtmlContent = $getHtmlContent;
exports.$getLexicalContent = $getLexicalContent;
exports.$insertDataTransferForPlainText = $insertDataTransferForPlainText;
exports.$insertDataTransferForRichText = $insertDataTransferForRichText;
exports.$insertGeneratedNodes = $insertGeneratedNodes;
exports.copyToClipboard = copyToClipboard;
exports.setLexicalClipboardDataTransfer = setLexicalClipboardDataTransfer;
+462
View File
@@ -0,0 +1,462 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html';
import { $addNodeStyle, $sliceSelectedTextNodeContent } from '@lexical/selection';
import { objectKlassEquals } from '@lexical/utils';
import { $isRangeSelection, $getSelection, $createTabNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $caretFromPoint, $isTextPointCaret, $getCaretRange, $getChildCaret, $getRoot, $isTextNode, $isElementNode, $parseSerializedNode, getDOMSelection, COPY_COMMAND, COMMAND_PRIORITY_CRITICAL, isSelectionWithinEditor, $getEditor, $cloneWithProperties } from 'lexical';
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
/**
* Returns the *currently selected* Lexical content as an HTML string, relying on the
* logic defined in the exportDOM methods on the LexicalNode classes. Note that
* this will not return the HTML content of the entire editor (unless all the content is included
* in the current selection).
*
* @param editor - LexicalEditor instance to get HTML content from
* @param selection - The selection to use (default is $getSelection())
* @returns a string of HTML content
*/
function $getHtmlContent(editor, selection = $getSelection()) {
if (selection == null) {
{
formatDevErrorMessage(`Expected valid LexicalSelection`);
}
}
// If we haven't selected anything
if ($isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
return '';
}
return $generateHtmlFromNodes(editor, selection);
}
/**
* Returns the *currently selected* Lexical content as a JSON string, relying on the
* logic defined in the exportJSON methods on the LexicalNode classes. Note that
* this will not return the JSON content of the entire editor (unless all the content is included
* in the current selection).
*
* @param editor - LexicalEditor instance to get the JSON content from
* @param selection - The selection to use (default is $getSelection())
* @returns
*/
function $getLexicalContent(editor, selection = $getSelection()) {
if (selection == null) {
{
formatDevErrorMessage(`Expected valid LexicalSelection`);
}
}
// If we haven't selected anything
if ($isRangeSelection(selection) && selection.isCollapsed() || selection.getNodes().length === 0) {
return null;
}
return JSON.stringify($generateJSONFromSelectedNodes(editor, selection));
}
/**
* Attempts to insert content of the mime-types text/plain or text/uri-list from
* the provided DataTransfer object into the editor at the provided selection.
* text/uri-list is only used if text/plain is not also provided.
*
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
*/
function $insertDataTransferForPlainText(dataTransfer, selection) {
const text = dataTransfer.getData('text/plain') || dataTransfer.getData('text/uri-list');
if (text != null) {
selection.insertRawText(text);
}
}
/**
* Attempts to insert content of the mime-types application/x-lexical-editor, text/html,
* text/plain, or text/uri-list (in descending order of priority) from the provided DataTransfer
* object into the editor at the provided selection.
*
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
* @param editor the LexicalEditor the content is being inserted into.
*/
function $insertDataTransferForRichText(dataTransfer, selection, editor) {
const lexicalString = dataTransfer.getData('application/x-lexical-editor');
if (lexicalString) {
try {
const payload = JSON.parse(lexicalString);
if (payload.namespace === editor._config.namespace && Array.isArray(payload.nodes)) {
const nodes = $generateNodesFromSerializedNodes(payload.nodes);
return $insertGeneratedNodes(editor, nodes, selection);
}
} catch (_unused) {
// Fail silently.
}
}
const htmlString = dataTransfer.getData('text/html');
const plainString = dataTransfer.getData('text/plain');
// Skip HTML handling if it matches the plain text representation.
// This avoids unnecessary processing for plain text strings created by
// iOS Safari autocorrect, which incorrectly includes a `text/html` type.
if (htmlString && plainString !== htmlString) {
try {
const parser = new DOMParser();
const dom = parser.parseFromString(trustHTML(htmlString), 'text/html');
const nodes = $generateNodesFromDOM(editor, dom);
return $insertGeneratedNodes(editor, nodes, selection);
} catch (_unused2) {
// Fail silently.
}
}
// Multi-line plain text in rich text mode pasted as separate paragraphs
// instead of single paragraph with linebreaks.
// Webkit-specific: Supports read 'text/uri-list' in clipboard.
const text = plainString || dataTransfer.getData('text/uri-list');
if (text != null) {
if ($isRangeSelection(selection)) {
const parts = text.split(/(\r?\n|\t)/);
if (parts[parts.length - 1] === '') {
parts.pop();
}
for (let i = 0; i < parts.length; i++) {
const currentSelection = $getSelection();
if ($isRangeSelection(currentSelection)) {
const part = parts[i];
if (part === '\n' || part === '\r\n') {
currentSelection.insertParagraph();
} else if (part === '\t') {
currentSelection.insertNodes([$createTabNode()]);
} else {
currentSelection.insertText(part);
}
}
}
} else {
selection.insertRawText(text);
}
}
}
function trustHTML(html) {
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const policy = window.trustedTypes.createPolicy('lexical', {
createHTML: input => input
});
return policy.createHTML(html);
}
return html;
}
/**
* Inserts Lexical nodes into the editor using different strategies depending on
* some simple selection-based heuristics. If you're looking for a generic way to
* to insert nodes into the editor at a specific selection point, you probably want
* {@link lexical.$insertNodes}
*
* @param editor LexicalEditor instance to insert the nodes into.
* @param nodes The nodes to insert.
* @param selection The selection to insert the nodes into.
*/
function $insertGeneratedNodes(editor, nodes, selection) {
if (!editor.dispatchCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, {
nodes,
selection
})) {
selection.insertNodes(nodes);
$updateSelectionOnInsert(selection);
}
return;
}
function $updateSelectionOnInsert(selection) {
if ($isRangeSelection(selection) && selection.isCollapsed()) {
const anchor = selection.anchor;
let nodeToInspect = null;
const anchorCaret = $caretFromPoint(anchor, 'previous');
if (anchorCaret) {
if ($isTextPointCaret(anchorCaret)) {
nodeToInspect = anchorCaret.origin;
} else {
const range = $getCaretRange(anchorCaret, $getChildCaret($getRoot(), 'next').getFlipped());
for (const caret of range) {
if ($isTextNode(caret.origin)) {
nodeToInspect = caret.origin;
break;
} else if ($isElementNode(caret.origin) && !caret.origin.isInline()) {
break;
}
}
}
}
if (nodeToInspect && $isTextNode(nodeToInspect)) {
const newFormat = nodeToInspect.getFormat();
const newStyle = nodeToInspect.getStyle();
if (selection.format !== newFormat || selection.style !== newStyle) {
selection.format = newFormat;
selection.style = newStyle;
selection.dirty = true;
}
}
}
}
function exportNodeToJSON(node) {
const serializedNode = node.exportJSON();
const nodeClass = node.constructor;
if (serializedNode.type !== nodeClass.getType()) {
{
formatDevErrorMessage(`LexicalNode: Node ${nodeClass.name} does not implement .exportJSON().`);
}
}
if ($isElementNode(node)) {
const serializedChildren = serializedNode.children;
if (!Array.isArray(serializedChildren)) {
{
formatDevErrorMessage(`LexicalNode: Node ${nodeClass.name} is an element but .exportJSON() does not have a children array.`);
}
}
}
return serializedNode;
}
function $appendNodesToJSON(editor, selection, currentNode, targetArray = []) {
let shouldInclude = selection !== null ? currentNode.isSelected(selection) : true;
const shouldExclude = $isElementNode(currentNode) && currentNode.excludeFromCopy('html');
let target = currentNode;
if (selection !== null) {
let clone = $cloneWithProperties(currentNode);
clone = $isTextNode(clone) && selection !== null ? $sliceSelectedTextNodeContent(selection, clone) : clone;
target = clone;
}
const children = $isElementNode(target) ? target.getChildren() : [];
const serializedNode = exportNodeToJSON(target);
// TODO: TextNode calls getTextContent() (NOT node.__text) within its exportJSON method
// which uses getLatest() to get the text from the original node with the same key.
// This is a deeper issue with the word "clone" here, it's still a reference to the
// same node as far as the LexicalEditor is concerned since it shares a key.
// We need a way to create a clone of a Node in memory with its own key, but
// until then this hack will work for the selected text extract use case.
if ($isTextNode(target)) {
const text = target.__text;
// If an uncollapsed selection ends or starts at the end of a line of specialized,
// TextNodes, such as code tokens, we will get a 'blank' TextNode here, i.e., one
// with text of length 0. We don't want this, it makes a confusing mess. Reset!
if (text.length > 0) {
serializedNode.text = text;
} else {
shouldInclude = false;
}
}
for (let i = 0; i < children.length; i++) {
const childNode = children[i];
const shouldIncludeChild = $appendNodesToJSON(editor, selection, childNode, serializedNode.children);
if (!shouldInclude && $isElementNode(currentNode) && shouldIncludeChild && currentNode.extractWithChild(childNode, selection, 'clone')) {
shouldInclude = true;
}
}
if (shouldInclude && !shouldExclude) {
targetArray.push(serializedNode);
} else if (Array.isArray(serializedNode.children)) {
for (let i = 0; i < serializedNode.children.length; i++) {
const serializedChildNode = serializedNode.children[i];
targetArray.push(serializedChildNode);
}
}
return shouldInclude;
}
// TODO why $ function with Editor instance?
/**
* Gets the Lexical JSON of the nodes inside the provided Selection.
*
* @param editor LexicalEditor to get the JSON content from.
* @param selection Selection to get the JSON content from.
* @returns an object with the editor namespace and a list of serializable nodes as JavaScript objects.
*/
function $generateJSONFromSelectedNodes(editor, selection) {
const nodes = [];
const root = $getRoot();
const topLevelChildren = root.getChildren();
for (let i = 0; i < topLevelChildren.length; i++) {
const topLevelNode = topLevelChildren[i];
$appendNodesToJSON(editor, selection, topLevelNode, nodes);
}
return {
namespace: editor._config.namespace,
nodes
};
}
/**
* This method takes an array of objects conforming to the BaseSerializedNode interface and returns
* an Array containing instances of the corresponding LexicalNode classes registered on the editor.
* Normally, you'd get an Array of BaseSerialized nodes from {@link $generateJSONFromSelectedNodes}
*
* @param serializedNodes an Array of objects conforming to the BaseSerializedNode interface.
* @returns an Array of Lexical Node objects.
*/
function $generateNodesFromSerializedNodes(serializedNodes) {
const nodes = [];
for (let i = 0; i < serializedNodes.length; i++) {
const serializedNode = serializedNodes[i];
const node = $parseSerializedNode(serializedNode);
if ($isTextNode(node)) {
$addNodeStyle(node);
}
nodes.push(node);
}
return nodes;
}
const EVENT_LATENCY = 50;
let clipboardEventTimeout = null;
// TODO custom selection
// TODO potentially have a node customizable version for plain text
/**
* Copies the content of the current selection to the clipboard in
* text/plain, text/html, and application/x-lexical-editor (Lexical JSON)
* formats.
*
* @param editor the LexicalEditor instance to copy content from
* @param event the native browser ClipboardEvent to add the content to.
* @returns
*/
async function copyToClipboard(editor, event, data) {
if (clipboardEventTimeout !== null) {
// Prevent weird race conditions that can happen when this function is run multiple times
// synchronously. In the future, we can do better, we can cancel/override the previously running job.
return false;
}
if (event !== null) {
return new Promise((resolve, reject) => {
editor.update(() => {
resolve($copyToClipboardEvent(editor, event, data));
});
});
}
const rootElement = editor.getRootElement();
const editorWindow = editor._window || window;
const windowDocument = window.document;
const domSelection = getDOMSelection(editorWindow);
if (rootElement === null || domSelection === null) {
return false;
}
const element = windowDocument.createElement('span');
element.style.cssText = 'position: fixed; top: -1000px;';
element.append(windowDocument.createTextNode('#'));
rootElement.append(element);
const range = new Range();
range.setStart(element, 0);
range.setEnd(element, 1);
domSelection.removeAllRanges();
domSelection.addRange(range);
return new Promise((resolve, reject) => {
const removeListener = editor.registerCommand(COPY_COMMAND, secondEvent => {
if (objectKlassEquals(secondEvent, ClipboardEvent)) {
removeListener();
if (clipboardEventTimeout !== null) {
window.clearTimeout(clipboardEventTimeout);
clipboardEventTimeout = null;
}
resolve($copyToClipboardEvent(editor, secondEvent, data));
}
// Block the entire copy flow while we wait for the next ClipboardEvent
return true;
}, COMMAND_PRIORITY_CRITICAL);
// If the above hack execCommand hack works, this timeout code should never fire. Otherwise,
// the listener will be quickly freed so that the user can reuse it again
clipboardEventTimeout = window.setTimeout(() => {
removeListener();
clipboardEventTimeout = null;
resolve(false);
}, EVENT_LATENCY);
windowDocument.execCommand('copy');
element.remove();
});
}
// TODO shouldn't pass editor (pass namespace directly)
function $copyToClipboardEvent(editor, event, data) {
if (data === undefined) {
const domSelection = getDOMSelection(editor._window);
if (!domSelection) {
return false;
}
const anchorDOM = domSelection.anchorNode;
const focusDOM = domSelection.focusNode;
if (anchorDOM !== null && focusDOM !== null && !isSelectionWithinEditor(editor, anchorDOM, focusDOM)) {
return false;
}
const selection = $getSelection();
if (selection === null) {
return false;
}
data = $getClipboardDataFromSelection(selection);
}
event.preventDefault();
const clipboardData = event.clipboardData;
if (clipboardData === null) {
return false;
}
setLexicalClipboardDataTransfer(clipboardData, data);
return true;
}
const clipboardDataFunctions = [['text/html', $getHtmlContent], ['application/x-lexical-editor', $getLexicalContent]];
/**
* Serialize the content of the current selection to strings in
* text/plain, text/html, and application/x-lexical-editor (Lexical JSON)
* formats (as available).
*
* @param selection the selection to serialize (defaults to $getSelection())
* @returns LexicalClipboardData
*/
function $getClipboardDataFromSelection(selection = $getSelection()) {
const clipboardData = {
'text/plain': selection ? selection.getTextContent() : ''
};
if (selection) {
const editor = $getEditor();
for (const [mimeType, $editorFn] of clipboardDataFunctions) {
const v = $editorFn(editor, selection);
if (v !== null) {
clipboardData[mimeType] = v;
}
}
}
return clipboardData;
}
/**
* Call setData on the given clipboardData for each MIME type present
* in the given data (from {@link $getClipboardDataFromSelection})
*
* @param clipboardData the event.clipboardData to populate from data
* @param data The lexical data
*/
function setLexicalClipboardDataTransfer(clipboardData, data) {
for (const k in data) {
const v = data[k];
if (v !== undefined) {
clipboardData.setData(k, v);
}
}
}
export { $generateJSONFromSelectedNodes, $generateNodesFromSerializedNodes, $getClipboardDataFromSelection, $getHtmlContent, $getLexicalContent, $insertDataTransferForPlainText, $insertDataTransferForRichText, $insertGeneratedNodes, copyToClipboard, setLexicalClipboardDataTransfer };
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict'
const LexicalClipboard = process.env.NODE_ENV !== 'production' ? require('./LexicalClipboard.dev.js') : require('./LexicalClipboard.prod.js');
module.exports = LexicalClipboard;
+77
View File
@@ -0,0 +1,77 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/
import type {
BaseSelection,
LexicalEditor,
RangeSelection,
LexicalNode,
} from 'lexical';
/*
* Rich Text
*/
declare export function $insertDataTransferForRichText(
dataTransfer: DataTransfer,
selection: BaseSelection,
editor: LexicalEditor,
): void;
declare export function $getHtmlContent(
editor: LexicalEditor,
selection?: BaseSelection,
): string | null;
declare export function $getLexicalContent(
editor: LexicalEditor,
): string | null;
declare export function $insertGeneratedNodes(
editor: LexicalEditor,
nodes: Array<LexicalNode>,
selection: BaseSelection,
): void;
/*
* Plain Text
*/
/*
* Export as JSON
*/
declare export function $insertDataTransferForPlainText(
dataTransfer: DataTransfer,
selection: RangeSelection,
): void;
interface BaseSerializedNode {
children?: Array<BaseSerializedNode>;
type: string;
version: number;
}
declare export function $generateJSONFromSelectedNodes<
SerializedNode: BaseSerializedNode,
>(
editor: LexicalEditor,
selection: BaseSelection | null,
): {
namespace: string,
nodes: Array<SerializedNode>,
};
declare export function $generateNodesFromSerializedNodes(
serializedNodes: Array<BaseSerializedNode>,
): Array<LexicalNode>;
declare export function copyToClipboard(
editor: LexicalEditor,
event: null | ClipboardEvent,
): Promise<boolean>;
+21
View File
@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import * as modDev from './LexicalClipboard.dev.mjs';
import * as modProd from './LexicalClipboard.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const $generateJSONFromSelectedNodes = mod.$generateJSONFromSelectedNodes;
export const $generateNodesFromSerializedNodes = mod.$generateNodesFromSerializedNodes;
export const $getClipboardDataFromSelection = mod.$getClipboardDataFromSelection;
export const $getHtmlContent = mod.$getHtmlContent;
export const $getLexicalContent = mod.$getLexicalContent;
export const $insertDataTransferForPlainText = mod.$insertDataTransferForPlainText;
export const $insertDataTransferForRichText = mod.$insertDataTransferForRichText;
export const $insertGeneratedNodes = mod.$insertGeneratedNodes;
export const copyToClipboard = mod.copyToClipboard;
export const setLexicalClipboardDataTransfer = mod.setLexicalClipboardDataTransfer;
+19
View File
@@ -0,0 +1,19 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalClipboard.dev.mjs') : import('./LexicalClipboard.prod.mjs'));
export const $generateJSONFromSelectedNodes = mod.$generateJSONFromSelectedNodes;
export const $generateNodesFromSerializedNodes = mod.$generateNodesFromSerializedNodes;
export const $getClipboardDataFromSelection = mod.$getClipboardDataFromSelection;
export const $getHtmlContent = mod.$getHtmlContent;
export const $getLexicalContent = mod.$getLexicalContent;
export const $insertDataTransferForPlainText = mod.$insertDataTransferForPlainText;
export const $insertDataTransferForRichText = mod.$insertDataTransferForRichText;
export const $insertGeneratedNodes = mod.$insertGeneratedNodes;
export const copyToClipboard = mod.copyToClipboard;
export const setLexicalClipboardDataTransfer = mod.setLexicalClipboardDataTransfer;
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import{$generateHtmlFromNodes as t,$generateNodesFromDOM as e}from"@lexical/html";import{$addNodeStyle as n,$sliceSelectedTextNodeContent as o}from"@lexical/selection";import{objectKlassEquals as r}from"@lexical/utils";import{$isRangeSelection as i,$getSelection as l,$createTabNode as s,SELECTION_INSERT_CLIPBOARD_NODES_COMMAND as c,$caretFromPoint as a,$isTextPointCaret as u,$getCaretRange as f,$getChildCaret as d,$getRoot as p,$isTextNode as g,$isElementNode as m,$parseSerializedNode as h,getDOMSelection as x,COPY_COMMAND as w,COMMAND_PRIORITY_CRITICAL as y,isSelectionWithinEditor as T,$getEditor as v,$cloneWithProperties as C}from"lexical";function D(t,...e){const n=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const t of e)o.append("v",t);throw n.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function N(e,n=l()){return null==n&&D(166),i(n)&&n.isCollapsed()||0===n.getNodes().length?"":t(e,n)}function S(t,e=l()){return null==e&&D(166),i(e)&&e.isCollapsed()||0===e.getNodes().length?null:JSON.stringify(E(t,e))}function R(t,e){const n=t.getData("text/plain")||t.getData("text/uri-list");null!=n&&e.insertRawText(n)}function A(t,n,o){const r=t.getData("application/x-lexical-editor");if(r)try{const t=JSON.parse(r);if(t.namespace===o._config.namespace&&Array.isArray(t.nodes)){return P(o,L(t.nodes),n)}}catch(t){}const c=t.getData("text/html"),a=t.getData("text/plain");if(c&&a!==c)try{const t=(new DOMParser).parseFromString(function(t){if(window.trustedTypes&&window.trustedTypes.createPolicy){return window.trustedTypes.createPolicy("lexical",{createHTML:t=>t}).createHTML(t)}return t}(c),"text/html");return P(o,e(o,t),n)}catch(t){}const u=a||t.getData("text/uri-list");if(null!=u)if(i(n)){const t=u.split(/(\r?\n|\t)/);""===t[t.length-1]&&t.pop();for(let e=0;e<t.length;e++){const n=l();if(i(n)){const o=t[e];"\n"===o||"\r\n"===o?n.insertParagraph():"\t"===o?n.insertNodes([s()]):n.insertText(o)}}}else n.insertRawText(u)}function P(t,e,n){t.dispatchCommand(c,{nodes:e,selection:n})||(n.insertNodes(e),function(t){if(i(t)&&t.isCollapsed()){const e=t.anchor;let n=null;const o=a(e,"previous");if(o)if(u(o))n=o.origin;else{const t=f(o,d(p(),"next").getFlipped());for(const e of t){if(g(e.origin)){n=e.origin;break}if(m(e.origin)&&!e.origin.isInline())break}}if(n&&g(n)){const e=n.getFormat(),o=n.getStyle();t.format===e&&t.style===o||(t.format=e,t.style=o,t.dirty=!0)}}}(n))}function _(t,e,n,r=[]){let i=null===e||n.isSelected(e);const l=m(n)&&n.excludeFromCopy("html");let s=n;if(null!==e){let t=C(n);t=g(t)&&null!==e?o(e,t):t,s=t}const c=m(s)?s.getChildren():[],a=function(t){const e=t.exportJSON(),n=t.constructor;if(e.type!==n.getType()&&D(58,n.name),m(t)){const t=e.children;Array.isArray(t)||D(59,n.name)}return e}(s);if(g(s)){const t=s.__text;t.length>0?a.text=t:i=!1}for(let o=0;o<c.length;o++){const r=c[o],l=_(t,e,r,a.children);!i&&m(n)&&l&&n.extractWithChild(r,e,"clone")&&(i=!0)}if(i&&!l)r.push(a);else if(Array.isArray(a.children))for(let t=0;t<a.children.length;t++){const e=a.children[t];r.push(e)}return i}function E(t,e){const n=[],o=p().getChildren();for(let r=0;r<o.length;r++){_(t,e,o[r],n)}return{namespace:t._config.namespace,nodes:n}}function L(t){const e=[];for(let o=0;o<t.length;o++){const r=t[o],i=h(r);g(i)&&n(i),e.push(i)}return e}let b=null;async function F(t,e,n){if(null!==b)return!1;if(null!==e)return new Promise(((o,r)=>{t.update((()=>{o(M(t,e,n))}))}));const o=t.getRootElement(),i=t._window||window,l=window.document,s=x(i);if(null===o||null===s)return!1;const c=l.createElement("span");c.style.cssText="position: fixed; top: -1000px;",c.append(l.createTextNode("#")),o.append(c);const a=new Range;return a.setStart(c,0),a.setEnd(c,1),s.removeAllRanges(),s.addRange(a),new Promise(((e,o)=>{const i=t.registerCommand(w,(o=>(r(o,ClipboardEvent)&&(i(),null!==b&&(window.clearTimeout(b),b=null),e(M(t,o,n))),!0)),y);b=window.setTimeout((()=>{i(),b=null,e(!1)}),50),l.execCommand("copy"),c.remove()}))}function M(t,e,n){if(void 0===n){const e=x(t._window);if(!e)return!1;const o=e.anchorNode,r=e.focusNode;if(null!==o&&null!==r&&!T(t,o,r))return!1;const i=l();if(null===i)return!1;n=J(i)}e.preventDefault();const o=e.clipboardData;return null!==o&&(k(o,n),!0)}const O=[["text/html",N],["application/x-lexical-editor",S]];function J(t=l()){const e={"text/plain":t?t.getTextContent():""};if(t){const n=v();for(const[o,r]of O){const i=r(n,t);null!==i&&(e[o]=i)}}return e}function k(t,e){for(const n in e){const o=e[n];void 0!==o&&t.setData(n,o)}}export{E as $generateJSONFromSelectedNodes,L as $generateNodesFromSerializedNodes,J as $getClipboardDataFromSelection,N as $getHtmlContent,S as $getLexicalContent,R as $insertDataTransferForPlainText,A as $insertDataTransferForRichText,P as $insertGeneratedNodes,F as copyToClipboard,k as setLexicalClipboardDataTransfer};
+5
View File
@@ -0,0 +1,5 @@
# `@lexical/clipboard`
[![See API Documentation](https://lexical.dev/img/see-api-documentation.svg)](https://lexical.dev/docs/api/modules/lexical_clipboard)
This package contains the functionality for the clipboard feature of Lexical.
+117
View File
@@ -0,0 +1,117 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { BaseSelection, LexicalEditor, LexicalNode } from 'lexical';
export interface LexicalClipboardData {
'text/html'?: string | undefined;
'application/x-lexical-editor'?: string | undefined;
'text/plain': string;
}
/**
* Returns the *currently selected* Lexical content as an HTML string, relying on the
* logic defined in the exportDOM methods on the LexicalNode classes. Note that
* this will not return the HTML content of the entire editor (unless all the content is included
* in the current selection).
*
* @param editor - LexicalEditor instance to get HTML content from
* @param selection - The selection to use (default is $getSelection())
* @returns a string of HTML content
*/
export declare function $getHtmlContent(editor: LexicalEditor, selection?: BaseSelection | null): string;
/**
* Returns the *currently selected* Lexical content as a JSON string, relying on the
* logic defined in the exportJSON methods on the LexicalNode classes. Note that
* this will not return the JSON content of the entire editor (unless all the content is included
* in the current selection).
*
* @param editor - LexicalEditor instance to get the JSON content from
* @param selection - The selection to use (default is $getSelection())
* @returns
*/
export declare function $getLexicalContent(editor: LexicalEditor, selection?: BaseSelection | null): null | string;
/**
* Attempts to insert content of the mime-types text/plain or text/uri-list from
* the provided DataTransfer object into the editor at the provided selection.
* text/uri-list is only used if text/plain is not also provided.
*
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
*/
export declare function $insertDataTransferForPlainText(dataTransfer: DataTransfer, selection: BaseSelection): void;
/**
* Attempts to insert content of the mime-types application/x-lexical-editor, text/html,
* text/plain, or text/uri-list (in descending order of priority) from the provided DataTransfer
* object into the editor at the provided selection.
*
* @param dataTransfer an object conforming to the [DataTransfer interface] (https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface)
* @param selection the selection to use as the insertion point for the content in the DataTransfer object
* @param editor the LexicalEditor the content is being inserted into.
*/
export declare function $insertDataTransferForRichText(dataTransfer: DataTransfer, selection: BaseSelection, editor: LexicalEditor): void;
/**
* Inserts Lexical nodes into the editor using different strategies depending on
* some simple selection-based heuristics. If you're looking for a generic way to
* to insert nodes into the editor at a specific selection point, you probably want
* {@link lexical.$insertNodes}
*
* @param editor LexicalEditor instance to insert the nodes into.
* @param nodes The nodes to insert.
* @param selection The selection to insert the nodes into.
*/
export declare function $insertGeneratedNodes(editor: LexicalEditor, nodes: Array<LexicalNode>, selection: BaseSelection): void;
export interface BaseSerializedNode {
children?: Array<BaseSerializedNode>;
type: string;
version: number;
}
/**
* Gets the Lexical JSON of the nodes inside the provided Selection.
*
* @param editor LexicalEditor to get the JSON content from.
* @param selection Selection to get the JSON content from.
* @returns an object with the editor namespace and a list of serializable nodes as JavaScript objects.
*/
export declare function $generateJSONFromSelectedNodes<SerializedNode extends BaseSerializedNode>(editor: LexicalEditor, selection: BaseSelection | null): {
namespace: string;
nodes: Array<SerializedNode>;
};
/**
* This method takes an array of objects conforming to the BaseSerializedNode interface and returns
* an Array containing instances of the corresponding LexicalNode classes registered on the editor.
* Normally, you'd get an Array of BaseSerialized nodes from {@link $generateJSONFromSelectedNodes}
*
* @param serializedNodes an Array of objects conforming to the BaseSerializedNode interface.
* @returns an Array of Lexical Node objects.
*/
export declare function $generateNodesFromSerializedNodes(serializedNodes: Array<BaseSerializedNode>): Array<LexicalNode>;
/**
* Copies the content of the current selection to the clipboard in
* text/plain, text/html, and application/x-lexical-editor (Lexical JSON)
* formats.
*
* @param editor the LexicalEditor instance to copy content from
* @param event the native browser ClipboardEvent to add the content to.
* @returns
*/
export declare function copyToClipboard(editor: LexicalEditor, event: null | ClipboardEvent, data?: LexicalClipboardData): Promise<boolean>;
/**
* Serialize the content of the current selection to strings in
* text/plain, text/html, and application/x-lexical-editor (Lexical JSON)
* formats (as available).
*
* @param selection the selection to serialize (defaults to $getSelection())
* @returns LexicalClipboardData
*/
export declare function $getClipboardDataFromSelection(selection?: BaseSelection | null): LexicalClipboardData;
/**
* Call setData on the given clipboardData for each MIME type present
* in the given data (from {@link $getClipboardDataFromSelection})
*
* @param clipboardData the event.clipboardData to populate from data
* @param data The lexical data
*/
export declare function setLexicalClipboardDataTransfer(clipboardData: DataTransfer, data: LexicalClipboardData): void;
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
export { $generateJSONFromSelectedNodes, $generateNodesFromSerializedNodes, $getClipboardDataFromSelection, $getHtmlContent, $getLexicalContent, $insertDataTransferForPlainText, $insertDataTransferForRichText, $insertGeneratedNodes, copyToClipboard, type LexicalClipboardData, setLexicalClipboardDataTransfer, } from './clipboard';
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@lexical/clipboard",
"description": "This package provides the copy/paste functionality for Lexical.",
"keywords": [
"lexical",
"editor",
"rich-text",
"copy",
"paste"
],
"license": "MIT",
"version": "0.35.0",
"main": "LexicalClipboard.js",
"types": "index.d.ts",
"dependencies": {
"@lexical/html": "0.35.0",
"@lexical/list": "0.35.0",
"@lexical/selection": "0.35.0",
"@lexical/utils": "0.35.0",
"lexical": "0.35.0"
},
"repository": {
"type": "git",
"url": "https://github.com/facebook/lexical",
"directory": "packages/lexical-clipboard"
},
"module": "LexicalClipboard.mjs",
"sideEffects": false,
"exports": {
".": {
"import": {
"types": "./index.d.ts",
"development": "./LexicalClipboard.dev.mjs",
"production": "./LexicalClipboard.prod.mjs",
"node": "./LexicalClipboard.node.mjs",
"default": "./LexicalClipboard.mjs"
},
"require": {
"types": "./index.d.ts",
"development": "./LexicalClipboard.dev.js",
"production": "./LexicalClipboard.prod.js",
"default": "./LexicalClipboard.js"
}
}
}
}