Skip to content

Instantly share code, notes, and snippets.

@cdaz5
Last active October 3, 2020 15:13
Show Gist options
  • Save cdaz5/040092a2b0adebd920acf92bab0637d5 to your computer and use it in GitHub Desktop.
Save cdaz5/040092a2b0adebd920acf92bab0637d5 to your computer and use it in GitHub Desktop.
const withEntitySearch = <P,>(Component: ComponentType<P>) => (props: P) => {
const [target, setTarget] = useState<Range | null | undefined>(null);
const [index, setIndex] = useState(0);
const [searchState, setSearch] = useState<SearchState>(({
searchTerm: '',
searchType: null,
} as unknown) as SearchState);
const optionsMenuRef = useRef<HTMLDivElement | null>(null);
// this is just a custom hook that calls an api
const { options } = useAlgoliaSearch({ ...searchState });
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
const onKeyDown = useCallback(
(event) => {
if (target) {
switch (event.key) {
case 'ArrowDown':
{
event.preventDefault();
const prevIndex = index >= options.length - 1 ? 0 : index + 1;
setIndex(prevIndex);
}
break;
case 'ArrowUp':
{
event.preventDefault();
const nextIndex = index <= 0 ? options.length - 1 : index - 1;
setIndex(nextIndex);
}
break;
case 'Tab':
case 'Enter':
event.preventDefault();
Transforms.select(editor, target);
insertEntity(editor, options[index], searchState.searchType);
setTarget(null);
break;
case 'Escape':
event.preventDefault();
setTarget(null);
break;
default:
break;
}
}
},
[index, searchState, target, options],
);
const handleEntitySearch = useCallback(() => {
const { selection } = editor;
if (selection && Range.isCollapsed(selection)) {
const [start] = Range.edges(selection);
const wordBefore = Editor.before(editor, start, { unit: 'word' });
const before = wordBefore && Editor.before(editor, wordBefore);
const beforeRange = before && Editor.range(editor, before, start);
const beforeText = beforeRange && Editor.string(editor, beforeRange);
const beforeMatch = beforeText && beforeText.match(/(^#dx|#sy)(\w+)$/);
const after = Editor.after(editor, start);
const afterRange = Editor.range(editor, start, after);
const afterText = Editor.string(editor, afterRange);
const afterMatch = afterText.match(/^(\s|$)/);
if (beforeMatch && afterMatch) {
const [, searchType, searchTerm] = beforeMatch;
setTarget(beforeRange);
setSearch({
searchTerm,
searchType,
});
setIndex(0);
return;
}
}
setTarget(null);
}, [editor]);
useEffect(() => {
if (optionsMenuRef?.current && target && options.length > 0) {
const el = optionsMenuRef.current;
const domRange = ReactEditor.toDOMRange(editor, target);
const rect = domRange.getBoundingClientRect();
el.style.top = `${rect.top + window.pageYOffset + 24}px`;
el.style.left = `${rect.left + window.pageXOffset}px`;
}
}, [options.length, editor, index, searchState, target]);
return (
<>
<Component
{...props}
ref={optionsMenuRef}
withEntitySearch
{...{
entityOptions: options,
slateEditor: editor,
onKeyDown,
searchState,
setSearch,
index,
setIndex,
setTarget,
target,
handleEntitySearch,
}}
/>
</>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment