UNPKG

@churchapps/apphelper-markdown

Version:
188 lines (187 loc) 9.9 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useCallback, useRef, useEffect } from "react"; import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; import { mergeRegister } from "@lexical/utils"; import { $isCustomLinkNode } from "./CustomLinkNode"; import { $getSelection, SELECTION_CHANGE_COMMAND, $isRangeSelection, $getNodeByKey } from "lexical"; import { TOGGLE_CUSTOM_LINK_NODE_COMMAND } from "./CustomLinkNode"; import { getSelectedNode } from "../ToolbarPlugin"; import { FormControl, InputLabel, Select, MenuItem, TextField, Button } from "@mui/material"; const positionEditorElement = (editor, rect) => { if (rect === null) { editor.style.opacity = "0"; editor.style.top = "-1000px"; editor.style.left = "-1000px"; } else { editor.style.opacity = "1"; // Add viewport height check const editorHeight = editor.offsetHeight; const viewportHeight = window.innerHeight; let topPosition = rect.top + rect.height + 10; // If editor would go off bottom of screen, position it above the selection instead if (topPosition + editorHeight > viewportHeight) { topPosition = rect.top - editorHeight - 10; } editor.style.top = `${topPosition}px`; // Ensure editor stays within horizontal bounds const leftPosition = Math.max(0, Math.min(rect.left + window.pageXOffset - editor.offsetWidth / 2 + rect.width / 2, window.innerWidth - editor.offsetWidth)); editor.style.left = `${leftPosition}px`; } }; const LowPriority = 1; const FloatingLinkEditor = ({ linkUrl, setLinkUrl, classNamesList, setClassNamesList, targetAttribute, setTargetAttribute, selectedElementKey }) => { const [editor] = useLexicalComposerContext(); const editorRef = useRef(null); const mouseDownRef = useRef(false); /* const [lastSelection, setLastSelection] = useState< GridSelection | NodeSelection | RangeSelection | null >(null); */ const updateLinkEditor = useCallback(() => { const selection = $getSelection(); if ($isRangeSelection(selection)) { const node = getSelectedNode(selection); const parent = node.getParent(); if ($isCustomLinkNode(parent)) { const _url = editor.getElementByKey(parent.__key)?.getAttribute("href"); if (_url) { setLinkUrl(_url); } } else if ($isCustomLinkNode(node)) { const _url = editor.getElementByKey(node.__key)?.getAttribute("href"); if (_url) { setLinkUrl(_url); } } } const editorElem = editorRef.current; const nativeSelection = window.getSelection(); if (!nativeSelection) return; const activeElement = document.activeElement; if (editorElem === null) { return; } const rootElement = editor.getRootElement(); if (selection !== null && !nativeSelection?.isCollapsed && rootElement !== null && rootElement.contains(nativeSelection.anchorNode)) { const domRange = nativeSelection.getRangeAt(0); let rect; if (nativeSelection.anchorNode === rootElement) { let inner = rootElement; while (inner.firstElementChild != null) { inner = inner.firstElementChild; } rect = inner.getBoundingClientRect(); } else { rect = domRange.getBoundingClientRect(); } if (!mouseDownRef.current) { positionEditorElement(editorElem, rect); } //setLastSelection(selection); } else if (!activeElement || activeElement.className !== "link-input") { positionEditorElement(editorElem, null); //setLastSelection(null); } return true; }, [editor]); //eslint-disable-line useEffect(() => (mergeRegister(editor.registerUpdateListener(({ editorState }) => { editorState.read(() => { updateLinkEditor(); }); }), editor.registerCommand(SELECTION_CHANGE_COMMAND, () => { editor.getEditorState().read(() => { updateLinkEditor(); }); return true; }, LowPriority))), [editor, updateLinkEditor]); useEffect(() => { editor.getEditorState().read(() => { updateLinkEditor(); }); }, [editor, updateLinkEditor]); useEffect(() => { editor.getEditorState().read(() => { updateLinkEditor(); }); }, []); //eslint-disable-line const variants = ["Light", "Light Accent", "Accent", "Dark Accent", "Dark", "Transparent Light", "Transparent Light Accent", "Transparent Accent", "Transparent Dark Accent", "Transparent Dark", "Primary", "Secondary", "Success", "Danger", "Warning", "Info"]; const sizes = ["Small", "Medium", "Large", "XL", "2X", "3X", "4X"]; let appearance = "link"; if (classNamesList[0].indexOf("btn") > -1) appearance = "btn"; if (classNamesList[0].indexOf("btn-block") > -1) appearance = "btn btn-block"; const handleSave = () => { editor.dispatchCommand(TOGGLE_CUSTOM_LINK_NODE_COMMAND, { url: linkUrl, classNames: classNamesList, target: targetAttribute }); editor.update(() => { if (!selectedElementKey) return; const selectedNode = $getNodeByKey(selectedElementKey); selectedNode?.selectEnd(); }); }; const getVariantKeyName = (variant) => { const keyNameParts = variant.split(" "); keyNameParts[0] = keyNameParts[0].toLowerCase(); return keyNameParts.join(""); }; const handleVariantChange = (e) => { const newArray = [...classNamesList]; let index = 0; newArray.forEach((item, i) => { variants.forEach((element) => { if (item.includes(getVariantKeyName(element))) { index = i; } ; }); }); newArray.splice(index, 1, e.target.value.toString()); setClassNamesList(newArray); }; const getVariantItems = () => { const result = []; variants.forEach((variant, idx) => { result.push(_jsx(MenuItem, { value: "btn-" + getVariantKeyName(variant), children: variant }, appearance + " btn-" + getVariantKeyName(variant))); if (idx === 4 || idx === 9) result.push(_jsx(MenuItem, { disabled: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" })); }); return result; }; return (_jsxs("div", { ref: editorRef, className: "link-editor", children: [_jsx(TextField, { label: "Url", value: linkUrl, onChange: e => { setLinkUrl(e.target.value); }, fullWidth: true, size: "small" }), _jsxs(FormControl, { fullWidth: true, children: [_jsx(InputLabel, { children: "Appearance" }), _jsxs(Select, { name: "classNames", fullWidth: true, label: "Appearance", size: "small", value: appearance, onChange: (e) => { let className = ""; if (e.target.value.toString() !== "link") className = e.target.value.toString(); setClassNamesList([className, "btn-primary", "btn-medium"]); }, children: [_jsx(MenuItem, { value: "link", children: "Standard Link" }), _jsx(MenuItem, { value: "btn", children: "Button" }), _jsx(MenuItem, { value: "btn btn-block", children: "Full Width Button" })] })] }), appearance !== "link" && _jsxs("div", { children: [_jsxs(FormControl, { fullWidth: true, children: [_jsx(InputLabel, { children: "Variant" }), _jsx(Select, { name: "classNames", fullWidth: true, label: "Variant", size: "small", value: classNamesList[1], onChange: handleVariantChange, children: getVariantItems() })] }), _jsxs(FormControl, { fullWidth: true, children: [_jsx(InputLabel, { children: "Size" }), _jsx(Select, { name: "classNames", fullWidth: true, label: "Size", size: "small", value: classNamesList[2], onChange: (e) => { const newArray = [...classNamesList]; let index = 0; newArray.forEach((item, i) => { sizes.forEach((element) => { if (item.includes(element.toLowerCase())) { index = i; } ; }); }); newArray.splice(index, 1, e.target.value.toString()); setClassNamesList(newArray); }, children: sizes.map((optionValue) => (_jsx(MenuItem, { value: "btn-" + optionValue.toLowerCase(), children: optionValue }, appearance + " btn-" + optionValue.toLowerCase()))) })] })] }), _jsxs("div", { className: "target-check", children: [_jsx("input", { type: "checkbox", checked: targetAttribute === "_blank", onChange: (e) => { setTargetAttribute((currentValue) => currentValue === "_blank" ? "_self" : "_blank"); } }), "- Open in new window"] }), _jsx("br", {}), _jsx(Button, { fullWidth: true, variant: "contained", onClick: handleSave, children: "Save" })] })); }; export default FloatingLinkEditor;