xuefengli
update
7362797
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the Chameleon License found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { $wrapNodeInElement, mergeRegister } from "@lexical/utils";
import {
$createParagraphNode,
$createRangeSelection,
$getSelection,
$insertNodes,
$isNodeSelection,
$isRootOrShadowRoot,
$setSelection,
COMMAND_PRIORITY_EDITOR,
COMMAND_PRIORITY_HIGH,
COMMAND_PRIORITY_LOW,
createCommand,
DRAGOVER_COMMAND,
DRAGSTART_COMMAND,
DROP_COMMAND,
LexicalCommand,
LexicalEditor,
} from "lexical";
import { useEffect } from "react";
import {
$createImageNode,
$isImageNode,
ImageNode,
ImagePayload,
} from "./ImageNode";
export type InsertImagePayload = Readonly<ImagePayload>;
const getDOMSelection = (targetWindow: Window | null): Selection | null =>
(targetWindow || window).getSelection();
export const INSERT_IMAGE_COMMAND: LexicalCommand<InsertImagePayload> =
createCommand("INSERT_IMAGE_COMMAND");
export function ImagesPlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
if (!editor.hasNodes([ImageNode])) {
throw new Error("ImagesPlugin: ImageNode not registered on editor");
}
return mergeRegister(
editor.registerCommand<InsertImagePayload>(
INSERT_IMAGE_COMMAND,
(payload) => {
const imageNode = $createImageNode(payload);
$insertNodes([imageNode]);
if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
$wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();
}
return true;
},
COMMAND_PRIORITY_EDITOR,
),
editor.registerCommand<DragEvent>(
DRAGSTART_COMMAND,
(event) => {
return onDragStart(event);
},
COMMAND_PRIORITY_HIGH,
),
editor.registerCommand<DragEvent>(
DRAGOVER_COMMAND,
(event) => {
return onDragover(event);
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand<DragEvent>(
DROP_COMMAND,
(event) => {
return onDrop(event, editor);
},
COMMAND_PRIORITY_HIGH,
),
);
}, [editor]);
return null;
}
const TRANSPARENT_IMAGE =
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
const img = document.createElement("img");
img.src = TRANSPARENT_IMAGE;
function onDragStart(event: DragEvent): boolean {
const node = getImageNodeInSelection();
if (!node) {
return false;
}
const dataTransfer = event.dataTransfer;
if (!dataTransfer) {
return false;
}
dataTransfer.setData("text/plain", "_");
dataTransfer.setDragImage(img, 0, 0);
dataTransfer.setData(
"application/x-lexical-drag",
JSON.stringify({
data: {
altText: node.__altText,
caption: node.__caption,
height: node.__height,
key: node.getKey(),
maxWidth: node.__maxWidth,
showCaption: node.__showCaption,
src: node.__src,
width: node.__width,
},
type: "image",
}),
);
return true;
}
function onDragover(event: DragEvent): boolean {
const node = getImageNodeInSelection();
if (!node) {
return false;
}
if (!canDropImage(event)) {
event.preventDefault();
}
return true;
}
function onDrop(event: DragEvent, editor: LexicalEditor): boolean {
const node = getImageNodeInSelection();
if (!node) {
return false;
}
const data = getDragImageData(event);
if (!data) {
return false;
}
event.preventDefault();
if (canDropImage(event)) {
const range = getDragSelection(event);
node.remove();
const rangeSelection = $createRangeSelection();
if (range !== null && range !== undefined) {
rangeSelection.applyDOMRange(range);
}
$setSelection(rangeSelection);
editor.dispatchCommand(INSERT_IMAGE_COMMAND, data);
}
return true;
}
function getImageNodeInSelection(): ImageNode | null {
const selection = $getSelection();
if (!$isNodeSelection(selection)) {
return null;
}
const nodes = selection.getNodes();
const node = nodes[0];
return $isImageNode(node) ? node : null;
}
function getDragImageData(event: DragEvent): null | InsertImagePayload {
const dragData = event.dataTransfer?.getData("application/x-lexical-drag");
if (!dragData) {
return null;
}
const { type, data } = JSON.parse(dragData);
if (type !== "image") {
return null;
}
return data;
}
declare global {
interface DragEvent {
rangeOffset?: number;
rangeParent?: Node;
}
}
function canDropImage(event: DragEvent): boolean {
const target = event.target;
return !!(
target &&
target instanceof HTMLElement &&
!target.closest("code, span.editor-image") &&
target.parentElement &&
target.parentElement.closest("div.ContentEditable__root")
);
}
function getDragSelection(event: DragEvent): Range | null | undefined {
let range;
const target = event.target as null | Element | Document;
const targetWindow =
target == null
? null
: target.nodeType === 9
? (target as Document).defaultView
: (target as Element).ownerDocument.defaultView;
const domSelection = getDOMSelection(targetWindow);
if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(event.clientX, event.clientY);
} else if (event.rangeParent && domSelection !== null) {
domSelection.collapse(event.rangeParent, event.rangeOffset || 0);
range = domSelection.getRangeAt(0);
} else {
throw Error(`Cannot get the selection when dragging`);
}
return range;
}