From e88b952a6f512f9e7a9c798f6c5601e216a3e4bf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 25 Sep 2023 14:49:00 -0500 Subject: [PATCH] lexical: remove MentionPlugin, only insert mention when selected from autosuggest --- src/features/compose/editor/index.tsx | 2 - .../compose/editor/nodes/mention-node.ts | 6 ++- .../editor/plugins/autosuggest-plugin.tsx | 31 ++++++++---- .../compose/editor/plugins/mention-plugin.tsx | 50 ------------------- 4 files changed, 25 insertions(+), 64 deletions(-) delete mode 100644 src/features/compose/editor/plugins/mention-plugin.tsx diff --git a/src/features/compose/editor/index.tsx b/src/features/compose/editor/index.tsx index 293922e465..c5bcfb48e6 100644 --- a/src/features/compose/editor/index.tsx +++ b/src/features/compose/editor/index.tsx @@ -23,7 +23,6 @@ import { useAppDispatch } from 'soapbox/hooks'; import { useNodes } from './nodes'; import AutosuggestPlugin from './plugins/autosuggest-plugin'; import FocusPlugin from './plugins/focus-plugin'; -import MentionPlugin from './plugins/mention-plugin'; import RefPlugin from './plugins/ref-plugin'; import StatePlugin from './plugins/state-plugin'; @@ -162,7 +161,6 @@ const ComposeEditor = React.forwardRef(({ /> - diff --git a/src/features/compose/editor/nodes/mention-node.ts b/src/features/compose/editor/nodes/mention-node.ts index 30dcb9b896..abc6909e6b 100644 --- a/src/features/compose/editor/nodes/mention-node.ts +++ b/src/features/compose/editor/nodes/mention-node.ts @@ -60,7 +60,11 @@ class MentionNode extends TextNode { } -const $createMentionNode = (text = ''): MentionNode => $applyNodeReplacement(new MentionNode(text)); +function $createMentionNode(text: string): MentionNode { + const node = new MentionNode(text); + node.setMode('token').toggleDirectionless(); + return $applyNodeReplacement(node); +} const $isMentionNode = ( node: LexicalNode | null | undefined, diff --git a/src/features/compose/editor/plugins/autosuggest-plugin.tsx b/src/features/compose/editor/plugins/autosuggest-plugin.tsx index 1770b983e0..51fcc47a7f 100644 --- a/src/features/compose/editor/plugins/autosuggest-plugin.tsx +++ b/src/features/compose/editor/plugins/autosuggest-plugin.tsx @@ -19,6 +19,7 @@ import { KEY_TAB_COMMAND, LexicalEditor, RangeSelection, + TextNode, } from 'lexical'; import React, { MutableRefObject, @@ -39,6 +40,7 @@ import { selectAccount } from 'soapbox/selectors'; import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions'; import AutosuggestAccount from '../../components/autosuggest-account'; +import { $createMentionNode } from '../nodes/mention-node'; import type { AutoSuggestion } from 'soapbox/components/autosuggest-input'; @@ -303,27 +305,29 @@ const AutosuggestPlugin = ({ dispatch((dispatch, getState) => { const state = editor.getEditorState(); const node = (state._selection as RangeSelection)?.anchor?.getNode(); + const { leadOffset, matchingString } = resolution!.match; + /** Offset for the beginning of the matched text, including the token. */ + const offset = leadOffset - 1; if (typeof suggestion === 'object') { if (!suggestion.id) return; - dispatch(useEmoji(suggestion)); // eslint-disable-line react-hooks/rules-of-hooks - const { leadOffset, matchingString } = resolution!.match; - if (isNativeEmoji(suggestion)) { - node.spliceText(leadOffset - 1, matchingString.length, `${suggestion.native} `, true); + node.spliceText(offset, matchingString.length, `${suggestion.native} `, true); } else { - node.spliceText(leadOffset - 1, matchingString.length, `${suggestion.colons} `, true); + node.spliceText(offset, matchingString.length, `${suggestion.colons} `, true); } } else if (suggestion[0] === '#') { node.setTextContent(`${suggestion} `); node.select(); } else { - const content = selectAccount(getState(), suggestion)!.acct; - - node.setTextContent(`@${content} `); - node.select(); + const acct = selectAccount(getState(), suggestion)!.acct; + const result = (node as TextNode).splitText(offset, offset + matchingString.length); + const textNode = result[1] ?? result[0]; + const mentionNode = textNode?.replace($createMentionNode(`@${acct}`)); + mentionNode.insertAfter(new TextNode(' ')); + mentionNode.selectNext(); } dispatch(clearComposeSuggestions(composeId)); @@ -337,13 +341,18 @@ const AutosuggestPlugin = ({ if (!node) return null; - if (['mention', 'hashtag'].includes(node.getType())) { + if (['hashtag'].includes(node.getType())) { const matchingString = node.getTextContent(); return { leadOffset: 0, matchingString }; } if (node.getType() === 'text') { - const [leadOffset, matchingString] = textAtCursorMatchesToken(node.getTextContent(), (state._selection as RangeSelection)?.anchor?.offset, [':']); + const [leadOffset, matchingString] = textAtCursorMatchesToken( + node.getTextContent(), + (state._selection as RangeSelection)?.anchor?.offset, + [':', '@'], + ); + if (!leadOffset || !matchingString) return null; return { leadOffset, matchingString }; } diff --git a/src/features/compose/editor/plugins/mention-plugin.tsx b/src/features/compose/editor/plugins/mention-plugin.tsx deleted file mode 100644 index 4a9df94d0f..0000000000 --- a/src/features/compose/editor/plugins/mention-plugin.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/** - * This source code is derived from code from Meta Platforms, Inc. - * and affiliates, licensed under the MIT license located in the - * LICENSE file in the /src/features/compose/editor directory. - */ - -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; -import { useLexicalTextEntity } from '@lexical/react/useLexicalTextEntity'; -import { useCallback, useEffect } from 'react'; - -import { $createMentionNode, MentionNode } from '../nodes/mention-node'; - -import type { TextNode } from 'lexical'; - -const MENTION_REGEX = /(?:^|\s)@[^\s]+/i; - -const MentionPlugin = (): JSX.Element | null => { - const [editor] = useLexicalComposerContext(); - - useEffect(() => { - if (!editor.hasNodes([MentionNode])) { - throw new Error('MentionPlugin: MentionNode not registered on editor'); - } - }, [editor]); - - const createNode = useCallback((textNode: TextNode): MentionNode => { - return $createMentionNode(textNode.getTextContent()); - }, []); - - const getMatch = useCallback((text: string) => { - const match = MENTION_REGEX.exec(text); - if (!match) return null; - - const length = match[0].length; - const start = match.index; - const end = start + length; - - return { start, end }; - }, []); - - useLexicalTextEntity( - getMatch, - MentionNode, - createNode, - ); - - return null; -}; - -export default MentionPlugin;