From a7d91fa15aa099058918a235eea7447e550d8da6 Mon Sep 17 00:00:00 2001 From: Carrotzpc Date: Wed, 10 Jul 2024 15:41:04 +0800 Subject: [PATCH] feat(Mentions): fix copy paste and support history --- src/Mentions/demos/index.tsx | 12 ++- src/Mentions/index.tsx | 79 ++++++++++--------- .../plugins/mention-node/component.tsx | 32 +++----- src/Mentions/plugins/mention-node/index.tsx | 24 ++---- src/Mentions/plugins/mention-node/node.tsx | 33 ++------ .../plugins/mention-node/replacement.tsx | 8 +- src/Mentions/plugins/mention-node/style.ts | 6 +- src/Mentions/plugins/mention-node/utils.ts | 1 - src/Mentions/provider.tsx | 23 ++++++ 9 files changed, 101 insertions(+), 117 deletions(-) create mode 100644 src/Mentions/provider.tsx diff --git a/src/Mentions/demos/index.tsx b/src/Mentions/demos/index.tsx index 36b5a8e..b4054f0 100644 --- a/src/Mentions/demos/index.tsx +++ b/src/Mentions/demos/index.tsx @@ -10,14 +10,22 @@ export default () => { }} defaultValue="👋,I'm {{1.zhang}}" options={[ - { label: 'zhang', value: '1.zhang', icon: }, + { + label: 'zhang', + value: '1.zhang', + icon: , + }, { label: 'luobo', value: '2.luobo', icon: , error: '选我触发错误样式', }, - { label: 'yunti', value: '3.yunti', icon: }, + { + label: 'yunti', + value: '3.yunti', + icon: , + }, ]} preTriggerChars=".*" triggers={['@']} diff --git a/src/Mentions/index.tsx b/src/Mentions/index.tsx index 8d95e4b..851defa 100644 --- a/src/Mentions/index.tsx +++ b/src/Mentions/index.tsx @@ -1,6 +1,7 @@ import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import type { EditorState } from 'lexical'; @@ -15,6 +16,7 @@ import { } from './plugins/mention-node'; import { MentionPickerPlugin, type MentionPickerPluginProps } from './plugins/mention-picker'; import OnBlurBlock from './plugins/on-blur-or-focus-block'; +import { MentionsConfigProvider } from './provider'; import { useStyles } from './style'; import type { AutoSize, MentionsOptionsMap } from './types'; import { textToEditorState } from './utils'; @@ -73,8 +75,8 @@ export const Mentions: React.FC = ({ onError: (error: Error) => { throw error; }, - // eslint-disable-next-line react-hooks/exhaustive-deps }), + // eslint-disable-next-line react-hooks/exhaustive-deps [] ); @@ -97,42 +99,45 @@ export const Mentions: React.FC = ({ return ( -
- - } - placeholder={ -
- {placeholder || `输入 ${triggers.join(' 或 ')} 插入引用`} -
- } - /> - - - - - -
+ +
+ + } + placeholder={ +
+ {placeholder || `输入 ${triggers.join(' 或 ')} 插入引用`} +
+ } + /> + + + + + + +
+
); }; diff --git a/src/Mentions/plugins/mention-node/component.tsx b/src/Mentions/plugins/mention-node/component.tsx index 49a7ccb..3845903 100644 --- a/src/Mentions/plugins/mention-node/component.tsx +++ b/src/Mentions/plugins/mention-node/component.tsx @@ -1,47 +1,33 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; -import { mergeRegister } from '@lexical/utils'; import { Icon } from '@lobehub/ui'; import { Flex, Tooltip } from 'antd'; -import { COMMAND_PRIORITY_EDITOR } from 'lexical'; import { CircleAlert } from 'lucide-react'; -import React, { memo, useEffect, useState } from 'react'; +import React, { memo, useEffect } from 'react'; + +import { useOptionsMap } from '@/Mentions/provider'; import { useSelectOrDelete } from '../../hooks'; -import { MentionsOptionsMap } from '../../types'; import { MentionNode } from './node'; import { useStyles } from './style'; -import { DELETE_MENTION_COMMAND, UPDATE_MENTIONS_OPTIONS } from './utils'; +import { DELETE_MENTION_COMMAND } from './utils'; export interface MentionNodeComponentProps { nodeKey: string; variable: string; - optionsMap: MentionsOptionsMap; } export const MentionNodeComponent: React.FC = memo( - ({ nodeKey, variable, optionsMap = {} }) => { + ({ nodeKey, variable }) => { + const optionsMap = useOptionsMap(); const [editor] = useLexicalComposerContext(); const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_MENTION_COMMAND); - const [localMentionsOptionsMap, setLocalMentionsOptionsMap] = - useState(optionsMap); - const option = localMentionsOptionsMap?.[variable]; + const option = optionsMap?.[variable]; const { styles } = useStyles({ isSelected, isError: !option || !!option.error }); useEffect(() => { - if (!editor.hasNodes([MentionNode])) + if (!editor.hasNodes([MentionNode])) { throw new Error('MentionsNodePlugin: MentionNode not registered on editor'); - - return mergeRegister( - editor.registerCommand( - UPDATE_MENTIONS_OPTIONS, - (newOptionsMap: MentionsOptionsMap) => { - setLocalMentionsOptionsMap(newOptionsMap); - - return true; - }, - COMMAND_PRIORITY_EDITOR - ) - ); + } }, [editor]); const Item = ( diff --git a/src/Mentions/plugins/mention-node/index.tsx b/src/Mentions/plugins/mention-node/index.tsx index 40f4901..abd502c 100644 --- a/src/Mentions/plugins/mention-node/index.tsx +++ b/src/Mentions/plugins/mention-node/index.tsx @@ -3,14 +3,8 @@ import { mergeRegister } from '@lexical/utils'; import { $insertNodes, COMMAND_PRIORITY_EDITOR } from 'lexical'; import React, { memo, useEffect } from 'react'; -import { MentionsOptionsMap } from '../../types'; import { $createMentionNode, MentionNode } from './node'; -import { - CLEAR_HIDE_MENU_TIMEOUT, - DELETE_MENTION_COMMAND, - INSERT_MENTION_COMMAND, - UPDATE_MENTIONS_OPTIONS, -} from './utils'; +import { CLEAR_HIDE_MENU_TIMEOUT, DELETE_MENTION_COMMAND, INSERT_MENTION_COMMAND } from './utils'; export * from './node'; export * from './replacement'; @@ -19,18 +13,11 @@ export * from './utils'; export interface MentionNodePluginProps { onInsert?: () => void; onDelete?: () => void; - optionsMap: MentionsOptionsMap; } export const MentionNodePlugin: React.FC = memo( - ({ optionsMap, onInsert, onDelete }) => { + ({ onInsert, onDelete }) => { const [editor] = useLexicalComposerContext(); - useEffect(() => { - editor.update(() => { - editor.dispatchCommand(UPDATE_MENTIONS_OPTIONS, optionsMap); - }); - }, [editor, optionsMap]); - useEffect(() => { if (!editor.hasNodes([MentionNode])) throw new Error('MentionsNodePlugin: MentionNode not registered on editor'); @@ -39,9 +26,8 @@ export const MentionNodePlugin: React.FC = memo( editor.registerCommand( INSERT_MENTION_COMMAND, (variable: string) => { - // eslint-disable-next-line unicorn/no-useless-undefined - editor.dispatchCommand(CLEAR_HIDE_MENU_TIMEOUT, undefined); - const mentionNode = $createMentionNode(variable, optionsMap); + editor.dispatchCommand(CLEAR_HIDE_MENU_TIMEOUT, null); + const mentionNode = $createMentionNode(variable); $insertNodes([mentionNode]); if (onInsert) onInsert(); @@ -60,7 +46,7 @@ export const MentionNodePlugin: React.FC = memo( COMMAND_PRIORITY_EDITOR ) ); - }, [editor, onInsert, onDelete, optionsMap]); + }, [editor, onInsert, onDelete]); return null; } diff --git a/src/Mentions/plugins/mention-node/node.tsx b/src/Mentions/plugins/mention-node/node.tsx index 5364d8d..2a329b8 100644 --- a/src/Mentions/plugins/mention-node/node.tsx +++ b/src/Mentions/plugins/mention-node/node.tsx @@ -7,30 +7,27 @@ import { MentionNodeComponent } from './component'; export type SerializedNode = SerializedLexicalNode & { variable: string; - optionsMap: MentionsOptionsMap; }; export class MentionNode extends DecoratorNode { __variable: string; - __optionsMap: MentionsOptionsMap; static getType(): string { return 'mention-node'; } static clone(node: MentionNode): MentionNode { - return new MentionNode(node.__variable, node.__optionsMap); + return new MentionNode(node.__variable); } isInline(): boolean { return true; } - constructor(variable: string, optionsMap: MentionsOptionsMap, key?: NodeKey) { + constructor(variable: string, optionsMap?: MentionsOptionsMap, key?: NodeKey) { super(key); this.__variable = variable; - this.__optionsMap = optionsMap; } createDOM(): HTMLElement { @@ -38,9 +35,6 @@ export class MentionNode extends DecoratorNode { div.style.display = 'inline-flex'; div.style.alignItems = 'center'; div.style.verticalAlign = 'middle'; - // div.style['align-items'] = 'center'; - // div.style['vertical-align'] = 'middle'; - return div; } @@ -49,18 +43,12 @@ export class MentionNode extends DecoratorNode { } decorate(): JSX.Element { - return ( - - ); + return ; } static importJSON(serializedNode: SerializedNode): MentionNode { // eslint-disable-next-line @typescript-eslint/no-use-before-define - const node = $createMentionNode(serializedNode.variable, serializedNode.optionsMap); + const node = $createMentionNode(serializedNode.variable); return node; } @@ -70,7 +58,6 @@ export class MentionNode extends DecoratorNode { type: 'mention-node', version: 1, variable: this.getVariable(), - optionsMap: this.getOptionsMap(), }; } @@ -79,21 +66,13 @@ export class MentionNode extends DecoratorNode { return self.__variable; } - getOptionsMap(): MentionsOptionsMap { - const self = this.getLatest(); - return self.__optionsMap; - } - getTextContent(): string { return `{{${this.getVariable()}}}`; } } -export function $createMentionNode( - variable: string, - workflowNodesMap: MentionsOptionsMap -): MentionNode { - return new MentionNode(variable, workflowNodesMap); +export function $createMentionNode(variable: string): MentionNode { + return new MentionNode(variable); } export function $isMentionNode( diff --git a/src/Mentions/plugins/mention-node/replacement.tsx b/src/Mentions/plugins/mention-node/replacement.tsx index c80d9de..78554ad 100644 --- a/src/Mentions/plugins/mention-node/replacement.tsx +++ b/src/Mentions/plugins/mention-node/replacement.tsx @@ -4,19 +4,17 @@ import type { TextNode } from 'lexical'; import { $applyNodeReplacement } from 'lexical'; import React, { memo, useCallback, useEffect } from 'react'; -import { MentionsOptionsMap } from '../../types'; import { decoratorTransform } from '../../utils'; import { CustomTextNode } from '../custom-text/node'; import { $createMentionNode, MentionNode } from './node'; import { MENTION_REGEX } from './utils'; export interface MentionNodePluginReplacementProps { - optionsMap: MentionsOptionsMap; onInsert?: () => void; } export const MentionNodePluginReplacement: React.FC = memo( - ({ optionsMap, onInsert }) => { + ({ onInsert }) => { const [editor] = useLexicalComposerContext(); useEffect(() => { @@ -29,9 +27,9 @@ export const MentionNodePluginReplacement: React.FC { diff --git a/src/Mentions/plugins/mention-node/style.ts b/src/Mentions/plugins/mention-node/style.ts index b0395cf..1b39de0 100644 --- a/src/Mentions/plugins/mention-node/style.ts +++ b/src/Mentions/plugins/mention-node/style.ts @@ -22,10 +22,8 @@ export const useStyles = createStyles( } if (isSelected) { return { - // background: token.colorInfoBgHover, - background: token.colorInfoBg, + background: token.colorInfoBgHover, border: token.colorInfoBorder, - // color: token.colorInfoTextActive, color: token.colorInfoText, }; } @@ -38,6 +36,8 @@ export const useStyles = createStyles( const { background, border, color } = getColors(); return { root: css` + user-select: none; + margin: 1px 2px; padding: 0 4px; diff --git a/src/Mentions/plugins/mention-node/utils.ts b/src/Mentions/plugins/mention-node/utils.ts index 0232eba..1430f2f 100644 --- a/src/Mentions/plugins/mention-node/utils.ts +++ b/src/Mentions/plugins/mention-node/utils.ts @@ -3,6 +3,5 @@ import { createCommand } from 'lexical'; export const INSERT_MENTION_COMMAND = createCommand('INSERT_MENTION_COMMAND'); export const DELETE_MENTION_COMMAND = createCommand('DELETE_MENTION_COMMAND'); export const CLEAR_HIDE_MENU_TIMEOUT = createCommand('CLEAR_HIDE_MENU_TIMEOUT'); -export const UPDATE_MENTIONS_OPTIONS = createCommand('UPDATE_MENTIONS_OPTIONS'); export const MENTION_REGEX = /{{([\w-]{1,50}(\.[_a-z]\w{0,29}){1,10})}}/gi; diff --git a/src/Mentions/provider.tsx b/src/Mentions/provider.tsx new file mode 100644 index 0000000..b781464 --- /dev/null +++ b/src/Mentions/provider.tsx @@ -0,0 +1,23 @@ +import { ReactNode, createContext, memo, useContext } from 'react'; + +import { MentionsOptionsMap } from './types'; + +interface Value { + optionsMap: MentionsOptionsMap; +} + +export const MentionsConfigContext = createContext(null); + +export const MentionsConfigProvider = memo<{ children: ReactNode; value: Value }>( + ({ children, value }) => { + return ( + {children} + ); + } +); + +export const useOptionsMap = () => { + const config = useContext(MentionsConfigContext); + + return config?.optionsMap; +};