@selfcommunity/react-ui
Version:
React UI Components to integrate a Community created with SelfCommunity Platform.
139 lines (138 loc) • 6.07 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { $isAutoLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $findMatchingParent, mergeRegister } from '@lexical/utils';
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND } from 'lexical';
import { useCallback, useEffect, useState } from 'react';
import { getSelectedNode } from '../../../utils/editor';
import { isValidUrl } from '@selfcommunity/utils';
import { styled } from '@mui/material/styles';
import { IconButton, InputAdornment, Paper, Popper, TextField } from '@mui/material';
import Icon from '@mui/material/Icon';
import { PREFIX } from '../constants';
const classes = {
root: `${PREFIX}-floating-link-plugin-root`
};
const Root = styled(Popper, {
name: PREFIX,
slot: 'FloatingLinkPluginRoot'
})(() => ({}));
function FloatingLinkPlugin({ editor, isLink, setIsLink }) {
const [anchorEl, setAnchorEl] = useState(null);
const [linkUrl, setLinkUrl] = useState('');
const [hideForUrl, setHideForUrl] = useState(null);
const [lastSelection, setLastSelection] = useState(null);
const updateLinkEditor = useCallback(() => {
var _a;
const selection = $getSelection();
if ($isRangeSelection(selection)) {
const node = getSelectedNode(selection);
const parent = node.getParent();
if ($isLinkNode(parent)) {
setLinkUrl(parent.getURL());
}
else if ($isLinkNode(node)) {
setLinkUrl(node.getURL());
}
else {
setLinkUrl('');
}
}
const nativeSelection = window.getSelection();
const activeElement = document.activeElement;
const rootElement = editor.getRootElement();
if (selection !== null &&
nativeSelection !== null &&
rootElement !== null &&
rootElement.contains(nativeSelection.anchorNode) &&
editor.isEditable()) {
setAnchorEl((_a = nativeSelection.focusNode) === null || _a === void 0 ? void 0 : _a.parentElement);
setLastSelection(selection);
}
else if (!activeElement) {
setAnchorEl(null);
setLastSelection(null);
setLinkUrl('');
}
return true;
}, [editor]);
useEffect(() => {
if (linkUrl != hideForUrl) {
setHideForUrl(null);
}
}, [linkUrl]);
useEffect(() => {
return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
updateLinkEditor();
});
}), editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
updateLinkEditor();
return true;
}, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
if (isLink) {
setIsLink(false);
return true;
}
return false;
}, COMMAND_PRIORITY_HIGH));
}, [editor, updateLinkEditor, setIsLink, isLink]);
useEffect(() => {
editor.getEditorState().read(() => {
updateLinkEditor();
});
}, [editor, updateLinkEditor]);
const handleLinkSubmission = () => {
if (lastSelection !== null) {
if (linkUrl !== '') {
setHideForUrl(linkUrl);
editor.dispatchCommand(TOGGLE_LINK_COMMAND, isValidUrl(linkUrl) ? linkUrl : 'https://');
}
}
};
if (!isLink || linkUrl === hideForUrl) {
return null;
}
return (_jsx(Root, Object.assign({ className: classes.root, open: Boolean(anchorEl), anchorEl: anchorEl, placement: "right" }, { children: _jsx(Paper, { children: _jsx(TextField, { size: "small", value: linkUrl, variant: "outlined", onChange: (event) => {
setLinkUrl(event.target.value);
}, InputProps: {
endAdornment: (_jsxs(InputAdornment, Object.assign({ position: "end" }, { children: [_jsx(IconButton, Object.assign({ size: "small", tabIndex: 0, onClick: () => {
setIsLink(false);
} }, { children: _jsx(Icon, { children: "close" }) })), _jsx(IconButton, Object.assign({ size: "small", tabIndex: 1, onClick: handleLinkSubmission }, { children: _jsx(Icon, { children: "check" }) }))] })))
} }) }) })));
}
function useFloatingLinkEditorToolbar(editor) {
const [activeEditor, setActiveEditor] = useState(editor);
const [isLink, setIsLink] = useState(false);
const updateToolbar = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
const node = getSelectedNode(selection);
const linkParent = $findMatchingParent(node, $isLinkNode);
const autoLinkParent = $findMatchingParent(node, $isAutoLinkNode);
// We don't want this menu to open for auto links.
if (linkParent != null && autoLinkParent == null) {
setIsLink(true);
}
else {
setIsLink(false);
}
}
}, []);
useEffect(() => {
return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
updateToolbar();
});
}), editor.registerCommand(SELECTION_CHANGE_COMMAND, (_payload, newEditor) => {
updateToolbar();
setActiveEditor(newEditor);
return false;
}, COMMAND_PRIORITY_CRITICAL));
}, [editor, updateToolbar]);
return _jsx(FloatingLinkPlugin, { editor: activeEditor, isLink: isLink, setIsLink: setIsLink });
}
export default () => {
const [editor] = useLexicalComposerContext();
return useFloatingLinkEditorToolbar(editor);
};