UNPKG

phx-react

Version:

PHX REACT

203 lines • 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RIGHT_CLICK_IMAGE_COMMAND = void 0; exports.default = ImageComponent; const tslib_1 = require("tslib"); const LexicalAutoFocusPlugin_1 = require("@lexical/react/LexicalAutoFocusPlugin"); const LexicalCollaborationContext_1 = require("@lexical/react/LexicalCollaborationContext"); const LexicalCollaborationPlugin_1 = require("@lexical/react/LexicalCollaborationPlugin"); const LexicalComposerContext_1 = require("@lexical/react/LexicalComposerContext"); const LexicalHashtagPlugin_1 = require("@lexical/react/LexicalHashtagPlugin"); const LexicalHistoryPlugin_1 = require("@lexical/react/LexicalHistoryPlugin"); const LexicalNestedComposer_1 = require("@lexical/react/LexicalNestedComposer"); const LexicalRichTextPlugin_1 = require("@lexical/react/LexicalRichTextPlugin"); const useLexicalNodeSelection_1 = require("@lexical/react/useLexicalNodeSelection"); const utils_1 = require("@lexical/utils"); const lexical_1 = require("lexical"); const React = tslib_1.__importStar(require("react")); const react_1 = require("react"); const SettingsContext_1 = require("../context/SettingsContext"); const SharedHistoryContext_1 = require("../context/SharedHistoryContext"); const collaboration_1 = require("../lib/collaboration"); const LinkPlugin_1 = tslib_1.__importDefault(require("../plugins/LinkPlugin")); const TreeViewPlugin_1 = tslib_1.__importDefault(require("../plugins/TreeViewPlugin")); const ContentEditable_1 = tslib_1.__importDefault(require("../ui/ContentEditable")); const ImageResizer_1 = tslib_1.__importDefault(require("../ui/ImageResizer")); const Placeholder_1 = tslib_1.__importDefault(require("../ui/Placeholder")); const ImageNode_1 = require("./ImageNode"); const LexicalErrorBoundary_1 = require("../shared/LexicalErrorBoundary"); const imageCache = new Set(); exports.RIGHT_CLICK_IMAGE_COMMAND = (0, lexical_1.createCommand)('RIGHT_CLICK_IMAGE_COMMAND'); function useSuspenseImage(src) { if (!imageCache.has(src)) { throw new Promise((resolve) => { const img = new Image(); img.src = src; img.onload = () => { imageCache.add(src); resolve(null); }; }); } } function LazyImage({ altText, className, height, imageRef, maxWidth, src, width, }) { useSuspenseImage(src); return (React.createElement("img", { ref: imageRef, alt: altText, className: className || undefined, draggable: 'false', src: src, style: { height, maxWidth, width, } })); } function ImageComponent({ altText, caption, height, maxWidth, nodeKey, resizable, showCaption, src, width, }) { const imageRef = (0, react_1.useRef)(null); const buttonRef = (0, react_1.useRef)(null); const [isSelected, setSelected, clearSelection] = (0, useLexicalNodeSelection_1.useLexicalNodeSelection)(nodeKey); const [isResizing, setIsResizing] = (0, react_1.useState)(false); const { isCollabActive } = (0, LexicalCollaborationContext_1.useCollaborationContext)(); const [editor] = (0, LexicalComposerContext_1.useLexicalComposerContext)(); const [selection, setSelection] = (0, react_1.useState)(null); const activeEditorRef = (0, react_1.useRef)(null); const onDelete = (0, react_1.useCallback)((payload) => { if (isSelected && (0, lexical_1.$isNodeSelection)((0, lexical_1.$getSelection)())) { const event = payload; event.preventDefault(); const node = (0, lexical_1.$getNodeByKey)(nodeKey); if ((0, ImageNode_1.$isImageNode)(node)) { node.remove(); } } return false; }, [isSelected, nodeKey]); const onEnter = (0, react_1.useCallback)((event) => { const latestSelection = (0, lexical_1.$getSelection)(); const buttonElem = buttonRef.current; if (isSelected && (0, lexical_1.$isNodeSelection)(latestSelection) && latestSelection.getNodes().length === 1) { if (showCaption) { // Move focus into nested editor (0, lexical_1.$setSelection)(null); event.preventDefault(); caption.focus(); return true; } else if (buttonElem !== null && buttonElem !== document.activeElement) { event.preventDefault(); buttonElem.focus(); return true; } } return false; }, [caption, isSelected, showCaption]); const onEscape = (0, react_1.useCallback)((event) => { if (activeEditorRef.current === caption || buttonRef.current === event.target) { (0, lexical_1.$setSelection)(null); editor.update(() => { setSelected(true); const parentRootElement = editor.getRootElement(); if (parentRootElement !== null) { parentRootElement.focus(); } }); return true; } return false; }, [caption, editor, setSelected]); const onClick = (0, react_1.useCallback)((payload) => { const event = payload; if (isResizing) { return true; } if (event.target === imageRef.current) { if (event.shiftKey) { setSelected(!isSelected); } else { clearSelection(); setSelected(true); } return true; } return false; }, [isResizing, isSelected, setSelected, clearSelection]); const onRightClick = (0, react_1.useCallback)((event) => { editor.getEditorState().read(() => { const latestSelection = (0, lexical_1.$getSelection)(); const domElement = event.target; if (domElement.tagName === 'IMG' && (0, lexical_1.$isRangeSelection)(latestSelection) && latestSelection.getNodes().length === 1) { editor.dispatchCommand(exports.RIGHT_CLICK_IMAGE_COMMAND, event); } }); }, [editor]); (0, react_1.useEffect)(() => { let isMounted = true; const rootElement = editor.getRootElement(); const unregister = (0, utils_1.mergeRegister)(editor.registerUpdateListener(({ editorState }) => { if (isMounted) { setSelection(editorState.read(() => (0, lexical_1.$getSelection)())); } }), editor.registerCommand(lexical_1.SELECTION_CHANGE_COMMAND, (_, activeEditor) => { activeEditorRef.current = activeEditor; return false; }, lexical_1.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical_1.CLICK_COMMAND, onClick, lexical_1.COMMAND_PRIORITY_LOW), editor.registerCommand(exports.RIGHT_CLICK_IMAGE_COMMAND, onClick, lexical_1.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical_1.DRAGSTART_COMMAND, (event) => { if (event.target === imageRef.current) { // TODO This is just a temporary workaround for FF to behave like other browsers. // Ideally, this handles drag & drop too (and all browsers). event.preventDefault(); return true; } return false; }, lexical_1.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical_1.KEY_DELETE_COMMAND, onDelete, lexical_1.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical_1.KEY_BACKSPACE_COMMAND, onDelete, lexical_1.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical_1.KEY_ENTER_COMMAND, onEnter, lexical_1.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical_1.KEY_ESCAPE_COMMAND, onEscape, lexical_1.COMMAND_PRIORITY_LOW)); rootElement === null || rootElement === void 0 ? void 0 : rootElement.addEventListener('contextmenu', onRightClick); return () => { isMounted = false; unregister(); rootElement === null || rootElement === void 0 ? void 0 : rootElement.removeEventListener('contextmenu', onRightClick); }; }, [ clearSelection, editor, isResizing, isSelected, nodeKey, onDelete, onEnter, onEscape, onClick, onRightClick, setSelected, ]); const onResizeEnd = (nextWidth, nextHeight) => { // Delay hiding the resize bars for click case setTimeout(() => { setIsResizing(false); }, 200); editor.update(() => { const node = (0, lexical_1.$getNodeByKey)(nodeKey); if ((0, ImageNode_1.$isImageNode)(node)) { node.setWidthAndHeight(nextWidth, nextHeight); } }); }; const onResizeStart = () => { setIsResizing(true); }; const { historyState } = (0, SharedHistoryContext_1.useSharedHistoryContext)(); const { settings: { showNestedEditorTreeView }, } = (0, SettingsContext_1.useSettings)(); const draggable = isSelected && (0, lexical_1.$isNodeSelection)(selection) && !isResizing; const isFocused = isSelected || isResizing; return (React.createElement(react_1.Suspense, { fallback: null }, React.createElement(React.Fragment, null, React.createElement("div", { draggable: draggable }, React.createElement(LazyImage, { altText: altText, className: isFocused ? `focused ${(0, lexical_1.$isNodeSelection)(selection) ? 'draggable' : ''}` : null, height: height, imageRef: imageRef, maxWidth: maxWidth, src: src, width: width })), showCaption && (React.createElement("div", { className: 'image-caption-container' }, React.createElement(LexicalNestedComposer_1.LexicalNestedComposer, { initialEditor: caption }, React.createElement(LexicalAutoFocusPlugin_1.AutoFocusPlugin, null), React.createElement(LinkPlugin_1.default, null), React.createElement(LexicalHashtagPlugin_1.HashtagPlugin, null), isCollabActive ? (React.createElement(LexicalCollaborationPlugin_1.CollaborationPlugin, { id: caption.getKey(), providerFactory: collaboration_1.createWebsocketProvider, shouldBootstrap: true })) : (React.createElement(LexicalHistoryPlugin_1.HistoryPlugin, { externalHistoryState: historyState })), React.createElement(LexicalRichTextPlugin_1.RichTextPlugin, { contentEditable: React.createElement(ContentEditable_1.default, { className: 'ImageNode__contentEditable' }), ErrorBoundary: LexicalErrorBoundary_1.LexicalErrorBoundary, placeholder: React.createElement(Placeholder_1.default, { className: 'ImageNode__placeholder' }, "Enter a caption...") }), showNestedEditorTreeView === true ? React.createElement(TreeViewPlugin_1.default, null) : null))), resizable && (0, lexical_1.$isNodeSelection)(selection) && isFocused && (React.createElement(ImageResizer_1.default, { editor: editor, imageRef: imageRef, maxWidth: maxWidth, onResizeEnd: onResizeEnd, onResizeStart: onResizeStart }))))); } //# sourceMappingURL=ImageComponent.js.map