UNPKG

@mdxeditor/editor

Version:

React component for rich text markdown editing

328 lines (327 loc) 11.9 kB
import { $wrapNodeInElement, mergeRegister } from "@lexical/utils"; import { Signal, withLatestFrom, Cell, Action, mapTo, map } from "@mdxeditor/gurx"; import { $insertNodes, $isRootOrShadowRoot, $createParagraphNode, $getNodeByKey, COMMAND_PRIORITY_EDITOR, DRAGSTART_COMMAND, COMMAND_PRIORITY_HIGH, DRAGOVER_COMMAND, COMMAND_PRIORITY_LOW, DROP_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_CRITICAL, createCommand, $createRangeSelection, $setSelection, $getSelection, $isNodeSelection } from "lexical"; import { realmPlugin } from "../../RealmWithPlugins.js"; import { CAN_USE_DOM } from "../../utils/detectMac.js"; import { activeEditor$, createActiveEditorSubscription$, addImportVisitor$, addLexicalNode$, addExportVisitor$, addComposerChild$ } from "../core/index.js"; import { ImageDialog } from "./ImageDialog.js"; import { $createImageNode, ImageNode, $isImageNode } from "./ImageNode.js"; import { LexicalImageVisitor } from "./LexicalImageVisitor.js"; import { MdastImageVisitor, MdastHtmlImageVisitor, MdastJsxImageVisitor } from "./MdastImageVisitor.js"; const internalInsertImage$ = Signal((r) => { r.sub(r.pipe(internalInsertImage$, withLatestFrom(activeEditor$)), ([values, theEditor]) => { theEditor == null ? void 0 : theEditor.update(() => { const imageNode = $createImageNode({ altText: values.altText ?? "", src: values.src, title: values.title ?? "" }); $insertNodes([imageNode]); if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) { $wrapNodeInElement(imageNode, $createParagraphNode).selectEnd(); } }); }); }); const insertImage$ = Signal((r) => { r.sub(r.pipe(insertImage$, withLatestFrom(imageUploadHandler$)), ([values, imageUploadHandler]) => { const handler = (src) => { r.pub(internalInsertImage$, { ...values, src }); }; if ("file" in values) { imageUploadHandler == null ? void 0 : imageUploadHandler(values.file).then(handler).catch((e) => { throw e; }); } else { handler(values.src); } }); }); const imageAutocompleteSuggestions$ = Cell([]); const disableImageResize$ = Cell(false); const imageUploadHandler$ = Cell(null); const imagePreviewHandler$ = Cell(null); const imageDialogState$ = Cell( { type: "inactive" }, (r) => { r.sub( r.pipe(saveImage$, withLatestFrom(activeEditor$, imageUploadHandler$, imageDialogState$)), ([values, theEditor, imageUploadHandler, dialogState]) => { const handler = dialogState.type === "editing" ? (src) => { theEditor == null ? void 0 : theEditor.update(() => { const { nodeKey } = dialogState; const imageNode = $getNodeByKey(nodeKey); imageNode.setTitle(values.title); imageNode.setAltText(values.altText); imageNode.setSrc(src); }); r.pub(imageDialogState$, { type: "inactive" }); } : (src) => { r.pub(internalInsertImage$, { ...values, src }); r.pub(imageDialogState$, { type: "inactive" }); }; if (values.file.length > 0) { imageUploadHandler == null ? void 0 : imageUploadHandler(values.file.item(0)).then(handler).catch((e) => { throw e; }); } else if (values.src) { handler(values.src); } } ); r.pub(createActiveEditorSubscription$, (editor) => { const theUploadHandler = r.getValue(imageUploadHandler$); return mergeRegister( editor.registerCommand( 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( DRAGSTART_COMMAND, (event) => { return onDragStart(event); }, COMMAND_PRIORITY_HIGH ), editor.registerCommand( DRAGOVER_COMMAND, (event) => { return onDragover(event, !!theUploadHandler); }, COMMAND_PRIORITY_LOW ), editor.registerCommand( DROP_COMMAND, (event) => { return onDrop(event, editor, r.getValue(imageUploadHandler$)); }, COMMAND_PRIORITY_HIGH ), editor.registerCommand( PASTE_COMMAND, (event) => { var _a, _b; if (!theUploadHandler) { let fromWeb = Array.from(((_a = event.clipboardData) == null ? void 0 : _a.items) ?? []); fromWeb = fromWeb.filter((i) => i.type.includes("text")); if (!fromWeb.length || fromWeb.length === 0) { return true; } return false; } let cbPayload = Array.from(((_b = event.clipboardData) == null ? void 0 : _b.items) ?? []); cbPayload = cbPayload.filter((i) => i.type.includes("image")); if (!cbPayload.length || cbPayload.length === 0) { return false; } const imageUploadHandlerValue = r.getValue(imageUploadHandler$); Promise.all(cbPayload.map((file) => imageUploadHandlerValue(file.getAsFile()))).then((urls) => { urls.forEach((url) => { editor.dispatchCommand(INSERT_IMAGE_COMMAND, { src: url, altText: "" }); }); }).catch((e) => { throw e; }); return true; }, COMMAND_PRIORITY_CRITICAL ) ); }); } ); const openNewImageDialog$ = Action((r) => { r.link(r.pipe(openNewImageDialog$, mapTo({ type: "new" })), imageDialogState$); }); const openEditImageDialog$ = Signal((r) => { r.link( r.pipe( openEditImageDialog$, map((payload) => ({ type: "editing", ...payload })) ), imageDialogState$ ); }); const closeImageDialog$ = Action((r) => { r.link(r.pipe(closeImageDialog$, mapTo({ type: "inactive" })), imageDialogState$); }); const disableImageSettingsButton$ = Cell(false); const saveImage$ = Signal(); const imagePlugin = realmPlugin({ init(realm, params) { realm.pubIn({ [addImportVisitor$]: [MdastImageVisitor, MdastHtmlImageVisitor, MdastJsxImageVisitor], [addLexicalNode$]: ImageNode, [addExportVisitor$]: LexicalImageVisitor, [addComposerChild$]: (params == null ? void 0 : params.ImageDialog) ?? ImageDialog, [imageUploadHandler$]: (params == null ? void 0 : params.imageUploadHandler) ?? null, [imageAutocompleteSuggestions$]: (params == null ? void 0 : params.imageAutocompleteSuggestions) ?? [], [disableImageResize$]: Boolean(params == null ? void 0 : params.disableImageResize), [disableImageSettingsButton$]: Boolean(params == null ? void 0 : params.disableImageSettingsButton), [imagePreviewHandler$]: (params == null ? void 0 : params.imagePreviewHandler) ?? null }); }, update(realm, params) { realm.pubIn({ [imageUploadHandler$]: (params == null ? void 0 : params.imageUploadHandler) ?? null, [imageAutocompleteSuggestions$]: (params == null ? void 0 : params.imageAutocompleteSuggestions) ?? [], [disableImageResize$]: Boolean(params == null ? void 0 : params.disableImageResize), [imagePreviewHandler$]: (params == null ? void 0 : params.imagePreviewHandler) ?? null }); } }); const getDOMSelection = (targetWindow) => CAN_USE_DOM ? (targetWindow ?? window).getSelection() : null; const INSERT_IMAGE_COMMAND = createCommand("INSERT_IMAGE_COMMAND"); const TRANSPARENT_IMAGE = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; function onDragStart(event) { const node = getImageNodeInSelection(); if (!node) { return false; } const dataTransfer = event.dataTransfer; if (!dataTransfer) { return false; } dataTransfer.setData("text/plain", "_"); const img = document.createElement("img"); img.src = TRANSPARENT_IMAGE; dataTransfer.setDragImage(img, 0, 0); dataTransfer.setData( "application/x-lexical-drag", JSON.stringify({ data: { altText: node.__altText, title: node.__title, key: node.getKey(), src: node.__src }, type: "image" }) ); return true; } function onDragover(event, hasUploadHandler) { var _a; if (hasUploadHandler) { let cbPayload = Array.from(((_a = event.dataTransfer) == null ? void 0 : _a.items) ?? []); cbPayload = cbPayload.filter((i) => i.type.includes("image")); if (cbPayload.length > 0) { event.preventDefault(); return true; } } const node = getImageNodeInSelection(); if (!node) { return false; } if (!canDropImage(event)) { event.preventDefault(); } return true; } function onDrop(event, editor, imageUploadHandler) { var _a; let cbPayload = Array.from(((_a = event.dataTransfer) == null ? void 0 : _a.items) ?? []); cbPayload = cbPayload.filter((i) => i.type.includes("image")); if (cbPayload.length > 0) { if (imageUploadHandler !== null) { event.preventDefault(); Promise.all(cbPayload.map((image) => imageUploadHandler(image.getAsFile()))).then((urls) => { urls.forEach((url) => { editor.dispatchCommand(INSERT_IMAGE_COMMAND, { src: url, altText: "" }); }); }).catch((e) => { throw e; }); return true; } } 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 !== void 0) { rangeSelection.applyDOMRange(range); } $setSelection(rangeSelection); editor.dispatchCommand(INSERT_IMAGE_COMMAND, data); } return true; } function getImageNodeInSelection() { const selection = $getSelection(); if (!$isNodeSelection(selection)) { return null; } const nodes = selection.getNodes(); const node = nodes[0]; return $isImageNode(node) ? node : null; } function getDragImageData(event) { var _a; const dragData = (_a = event.dataTransfer) == null ? void 0 : _a.getData("application/x-lexical-drag"); if (!dragData) { return null; } const { type, data } = JSON.parse(dragData); if (type !== "image") { return null; } return data; } function canDropImage(event) { const target = event.target; return !!(target && target instanceof HTMLElement && target.parentElement); } function getDragSelection(event) { let range; const target = event.target; const targetWindow = target == null ? null : target.nodeType === 9 ? target.defaultView : target.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; } export { $createImageNode, $isImageNode, INSERT_IMAGE_COMMAND, ImageNode, closeImageDialog$, disableImageResize$, disableImageSettingsButton$, imageAutocompleteSuggestions$, imageDialogState$, imagePlugin, imagePreviewHandler$, imageUploadHandler$, insertImage$, openEditImageDialog$, openNewImageDialog$, saveImage$ };