@vnetwork/richtext-editor
Version:
react richtext editor, only using for internal purpose
104 lines (93 loc) • 3.97 kB
JavaScript
import _JSXStyle from "styled-jsx/style";
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React from "react";
import { Transforms, Range } from "slate";
import { ReactEditor, useSelected, useFocused } from "slate-react";
import { useAppContext } from "../../context/app";
import Portal from "../Portal";
import { PORTAL_GAP } from "../../constants";
import MentionItem from "./Item";
export const createMentionNode = character => ({
type: "mention",
character,
children: [{
text: ""
}]
});
export const MentionModal = ({
editor,
target,
search,
callback
}) => {
const ref = React.useRef();
const {
employees
} = useAppContext();
const mentions = employees.filter(employee => employee.email?.toString().toLowerCase().startsWith(search.toLowerCase())).slice(0, 10);
const handleOnClick = character => {
const {
selection
} = editor;
const mention = createMentionNode(character);
Transforms.select(editor, target);
if (Range.isCollapsed(selection)) {
Transforms.insertNodes(editor, mention);
}
callback();
};
React.useEffect(() => {
// perform update mention modal position follow editor range is focused
if (target && ref?.current) {
const currentSlateRange = ReactEditor.toDOMRange(editor, target);
const boundingClientRect = currentSlateRange.getBoundingClientRect();
const top = boundingClientRect.top + window.pageYOffset;
const left = boundingClientRect.left + window.pageXOffset;
ref.current.style.top = `${top + PORTAL_GAP}px`;
ref.current.style.left = `${left}px`;
ref.current.style.opacity = 1;
}
const handleOnClickOutside = event => {
// console.log("components.MentionModal:event(click:out)");
return !ref.current?.contains(event.target) ? Transforms.select(editor, target) : null;
}; // handle document click outside mention modal & remove event listener on unmount component
document.addEventListener("click", handleOnClickOutside);
return () => document.removeEventListener("click", handleOnClickOutside); // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement("div", {
ref: ref,
"data-cy": "richtext-mentions",
className: "jsx-151523523" + " " + "richtext-portal"
}, mentions.length !== 0 ? mentions.map((mention, index) => {
return /*#__PURE__*/React.createElement(MentionItem, {
mention: mention,
key: index,
callback: handleOnClick
});
}) : "components.MentionModal:empty"), /*#__PURE__*/React.createElement(_JSXStyle, {
id: "151523523"
}, ".richtext-portal.jsx-151523523{height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;background-color:white;border-radius:0.25rem;overflow:hidden;box-shadow:rgba(9,30,66,0.31) 0px 0px 1px, rgba(9,30,66,0.25) 0px 4px 8px -2px;position:absolute;z-index:1;left:0;opacity:0;-webkit-transition:opacity linear 0.25s;transition:opacity linear 0.25s;}"));
};
const Mention = ({
attributes,
children,
element
}) => {
const selected = useSelected();
const focused = useFocused();
return /*#__PURE__*/React.createElement("span", _extends({}, attributes, {
contentEditable: false,
"data-cy": `mention-${element.character.replace(" ", "-")}`,
style: {
padding: "3px 3px 2px",
margin: "0 1px",
verticalAlign: "baseline",
display: "inline-block",
borderRadius: "4px",
backgroundColor: "#eee",
fontSize: "0.9em",
boxShadow: selected && focused ? "0 0 0 2px #B4D5FF" : "none"
}
}), "@", element.character, children);
};
export default Mention;