From 400ae6d73775f70a8deaa804d67846d9a38069d9 Mon Sep 17 00:00:00 2001 From: Scott Newcomer Date: Wed, 6 Nov 2024 07:23:46 -0600 Subject: [PATCH] mention plugin improvements --- packages/plugins/mention/src/plugin/index.tsx | 7 +- .../plugins/mention/src/ui/MentionRender.tsx | 110 +++++++++++++++--- 2 files changed, 93 insertions(+), 24 deletions(-) diff --git a/packages/plugins/mention/src/plugin/index.tsx b/packages/plugins/mention/src/plugin/index.tsx index e7a4c812a..71ee4ed55 100644 --- a/packages/plugins/mention/src/plugin/index.tsx +++ b/packages/plugins/mention/src/plugin/index.tsx @@ -8,11 +8,8 @@ const Mention = new YooptaPlugin({ mention: { render: MentionRender, props: { - url: null, - target: '_blank', - rel: 'noreferrer', - character: null, - nodeType: 'inlineVoid', + initialMentions: null, + fetchMentions: () => null, }, }, }, diff --git a/packages/plugins/mention/src/ui/MentionRender.tsx b/packages/plugins/mention/src/ui/MentionRender.tsx index 8e66e4db4..1f94d2ff2 100644 --- a/packages/plugins/mention/src/ui/MentionRender.tsx +++ b/packages/plugins/mention/src/ui/MentionRender.tsx @@ -1,29 +1,101 @@ -import { PluginElementRenderProps } from '@yoopta/editor'; -import { useSelected } from 'slate-react'; +import React, { useState, useCallback, useEffect } from 'react'; +import { PluginElementRenderProps, generateId } from '@yoopta/editor'; +import { useSlate, useSelected } from 'slate-react'; +import { Transforms, Editor } from 'slate'; -const MentionRender = (props: PluginElementRenderProps) => { - const { url, target, rel, character } = props.element.props || {}; +const MENTION_TRIGGER = '@'; + +interface MentionRenderProps extends PluginElementRenderProps { + initialMentions: string[]; + fetchMentions: (query: string) => Promise; +} + +const MentionRender = (props: MentionRenderProps) => { + const { character } = props.element.props || {}; + const { initialMentions = [], fetchMentions } = props; const selected = useSelected(); + const editor = useSlate(); - const handleClick = (e) => { - e.preventDefault(); - }; + const [showMenu, setShowMenu] = useState(false); + const [search, setSearch] = useState(''); + const [mentionList, setMentionList] = useState(initialMentions); + const [highlightedIndex, setHighlightedIndex] = useState(0); const bgColor = selected ? 'yoo-m-bg-[#e2e2e2]' : 'yoo-m-bg-[#f4f4f5]'; + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key === MENTION_TRIGGER) { + setShowMenu(true); + setSearch(''); + setMentionList(initialMentions); // Populate list with initial mentions on open + } else if (showMenu) { + if (event.key === 'ArrowDown') { + event.preventDefault(); + setHighlightedIndex((prev) => (prev + 1) % mentionList.length); + } else if (event.key === 'ArrowUp') { + event.preventDefault(); + setHighlightedIndex((prev) => (prev - 1 + mentionList.length) % mentionList.length); + } else if (event.key === 'Enter') { + event.preventDefault(); + insertMention(editor, mentionList[highlightedIndex]); + setShowMenu(false); + } else if (event.key === 'Escape') { + setShowMenu(false); + } + } + }, + [showMenu, mentionList, highlightedIndex, editor, initialMentions], + ); + + useEffect(() => { + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [handleKeyDown]); + + useEffect(() => { + if (showMenu) { + fetchMentions(search).then(setMentionList); // should we offer fuzzy search here? + } + }, [search, showMenu, fetchMentions]); + + const insertMention = (editor: Editor, character: string) => { + const mention = { id: generateId(), type: 'mention', character, children: [{ text: '' }] }; + Transforms.insertNodes(editor, mention); // @todo fix type: 'mention' types + Transforms.move(editor); // move cursor after + }; + return ( - - {character ? `@${character}` : null} - {props.children} - + <> + + {character ? `@${character}` : null} + {props.children} + + + {showMenu && ( +
+ {mentionList.map((mention, index) => ( +
{ + insertMention(editor, mention); + setShowMenu(false); + }} + onMouseEnter={() => setHighlightedIndex(index)} + > + @{mention} +
+ ))} +
+ )} + ); };