phx-react
Version:
PHX REACT
203 lines • 11.2 kB
JavaScript
"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