如何将 Lexical 与 React-Hook-Form 集成以提交编辑器作为输入

问题描述 投票:0回答:3

当我尝试制作博客时,我无法通过表单中的编辑器。我发现了这个:

DraftJS React-Hook-Form - 提交编辑器作为输入

但是LexicalRichTextEditor似乎没有这样的标签可以传递。谁能帮我? 如何传递属性来实现添加内容和修改内容功能?

type LexicalEditorProps = {
    //config: Parameters<typeof LexicalComposer>["0"]["initialConfig"];
    content: any;
};

export default function MyEditor(props: LexicalEditorProps) {
    const [ editor ] = useLexicalComposerContext();
    const editorStateRef = useRef();
    const [saveContent, setSaveContent] = useState('');
    const editorConfig: any = {
        // The editor theme
        theme: EditorTheme,
        // Handling of errors during update
        onError(error: any) {
            throw error;
        },
        editorState: props.content,
        // Any custom nodes go here
        nodes: [
            HeadingNode,
            ListNode,
            ListItemNode,
            QuoteNode,
            CodeNode,
            CodeHighlightNode,
            TableNode,
            TableCellNode,
            TableRowNode,
            AutoLinkNode,
            LinkNode
        ]
    };

    useEffect(()=>{
        if(editorStateRef.current){
            setSaveContent(JSON.stringify(editorStateRef.current));
        }
        editor.update(()=>{
            const root = $getRoot();
            const selection = $getSelection();
            const paragraphNode = $createParagraphNode();
            const textNode = $createTextNode(saveContent);

            paragraphNode.append(textNode);
            root.append(paragraphNode);

        });
    },[saveContent]);
    return (
        <LexicalComposer initialConfig={editorConfig}>
            <div className="editor-container">
                <ToolbarPlugin />
                <div className="editor-inner">
                    <RichTextPlugin
                        contentEditable={<ContentEditable className="editor-input" />}
                        placeholder={<Placeholder />}
                        ErrorBoundary={LexicalErrorBoundary}
                    />
                    <OnChangePlugin onChange={(editorState:any) => editorStateRef.current = editorState} />
                    <HistoryPlugin />
                    <AutoFocusPlugin />
                    <CodeHighlightPlugin />
                    <ListPlugin />
                    <LinkPlugin />
                    <AutoLinkPlugin />
                    <ListMaxIndentLevelPlugin maxDepth={7} />
                    <MarkdownShortcutPlugin transformers={TRANSFORMERS} />
                </div>
            </div>
        </LexicalComposer>
    );
}

export function MyForm(){
 const {register, handleSubmit, control, formState: {errors}} = useForm();
    const onSubmit = ( data:any) => {      
        console.log(data);
    };
 return (
   <form onSubmit={handleSubmit(onSubmit)}>
                <Stack spacing={2}>                                      
                    <Card>
                        <Controller control={control} name="content" render={()=> (
                            <MyEditor content={dataSet.content} />
                        )} />
                    </Card>
                    <Box>
                        <Button variant="contained" type="submit">Save</Button>
                    </Box>
                </Stack>
            </form>
 );
}
reactjs text-editor react-hook-form rich-text-editor lexicaljs
3个回答
6
投票

我找到了解决方案,但这不是我做的,而是其他伟大的人帮助我解决了问题,并希望能帮助其他人。 https://codesandbox.io/s/purple-water-xf50bi?file=/src/App.tsx

更新:删除 if (editorRef.current !== undefined) 语句并将字段内容附加到数据

export default function App() {
  const schema = yup
    .object({
      title: yup.string().required(),
      category: yup.string().required(),
      tags: yup.array().required()
    })
    .required();
  const { register, handleSubmit } = useForm({
    resolver: yupResolver(schema)
  });

  //Get Editor State
  const editorRef: any = useRef();
  const onSubmit = (data: any) => {
    data.content= JSON.stringify(editorRef.current.getEditorState())
    console.log(data);
  };
  
  //if (editorRef.current !== undefined) {
  // if (editorRef.current !== null) {
  //    const latestEditorState = editorRef.current.getEditorState();
  //    const textContent = latestEditorState.read(() =>
  //      //You could change getTextContent() for your purpose
  //      $getRoot().getTextContent()
  //    );
  //    console.log(textContent);
  //  }
  //}
  return (
    <div className="App">
      <form onSubmit={handleSubmit(onSubmit)}>
        <Stack spacing={2}>
          <input {...register("title")} type="text" placeholder="Title" />
          <input {...register("category")} type="text" placeholder="Category" />
          <input type="" placeholder="Tags" />
          <select {...register("tags")} id="tags" multiple>
            <option value="nginx">nginx</option>
            <option value="java">java</option>
            <option value="react">react</option>
            <option value="mui">mui</option>
          </select>
          <Card elevation={3}>
            <MyEditor ref={editorRef} />
          </Card>
          <button type="submit">Save</button>
        </Stack>
      </form>
    </div>
  );
}

我的编辑器.tsx


function Placeholder() {
  return <div className="editor-placeholder">Enter some rich text...</div>;
}

const editorConfig: any = {
  // The editor theme
  theme: EditorTheme,
  // Handling of errors during update
  onError(error: any) {
    throw error;
  },
  // Any custom nodes go here
  nodes: [
    HeadingNode,
    ListNode,
    ListItemNode,
    QuoteNode,
    CodeNode,
    CodeHighlightNode,
    TableNode,
    TableCellNode,
    TableRowNode,
    AutoLinkNode,
    LinkNode
  ]
};

// ADDED THIS:
const EditorCapturePlugin = React.forwardRef((props: any, ref: any) => {
  const [editor] = useLexicalComposerContext();
  useEffect(() => {
    ref.current = editor;
    return () => {
      ref.current = null;
    };
  }, [editor, ref]);

  return null;
});

export const MyEditor = React.forwardRef((props: any, ref: any) => {
  return (
    <LexicalComposer initialConfig={editorConfig}>
      <div className="editor-container">
        <ToolbarPlugin />
        <div className="editor-inner">
          <RichTextPlugin
            contentEditable={<ContentEditable className="editor-input" />}
            placeholder={<Placeholder />}
            ErrorBoundary={LexicalErrorBoundary}
          />
          {/* ADDED THIS: */}
          <EditorCapturePlugin ref={ref} />
          <HistoryPlugin />
          <AutoFocusPlugin />
          <CodeHighlightPlugin />
          <ListPlugin />
          <LinkPlugin />
          <AutoLinkPlugin />
          <ListMaxIndentLevelPlugin maxDepth={7} />
          <MarkdownShortcutPlugin transformers={TRANSFORMERS} />
        </div>
      </div>
    </LexicalComposer>
  );
});

1
投票

不太漂亮,但效果很好,在这种情况下,我需要将 editorState (以便能够在将其保存到数据库后编辑字段)和内容本身存储在 html 中

  <Controller
    control={control}
    name={`${fieldName}.payload.content`}
    render={({ field: fieldContent }) => (
      <Controller
        control={control}
        name={`${fieldName}.payload.editorState`}
        render={({ field: fieldEditorState }) => (
          <RichText
            background={backgroundColor}
            placeholder={t('create-campaign-form:create-text-for-campaign')}
            onChange={value => {
              fieldEditorState.onChange(value.editorState)
              fieldContent.onChange(value.content)
            }}
            value={fieldEditorState.value}
          />
        )}
      />
    )}
  />

属性

value
onChange
的使用方式如下

    // RichText.tsx
    const RichText = ({
      placeholder = '',
      onChange,
      value,
      background,
    }: EditorProps) => {
      const placeholderElement = <PlaceholderElement placeholder={placeholder} />
      const [floatingAnchorElem, setFloatingAnchorElem] =
        useState<HTMLDivElement | null>(null)
    
      const onRef = (_floatingAnchorElem: HTMLDivElement) => {
        if (_floatingAnchorElem !== null) {
          setFloatingAnchorElem(_floatingAnchorElem)
        }
      }
    
      const initialConfig: InitialConfigType = {
        namespace: 'RichTextEditor',
        nodes: [...Nodes],
        onError: (error: Error) => {
          throw error
        },
        theme,
        ...(value && {
          editorState: typeof value !== 'string' ? JSON.stringify(value) : value,
        }),
      }
    
      return (
        <LexicalComposer initialConfig={initialConfig}>
          <ToolbarPlugin />
          <EditorContainer background={background}>
            <LexicalAutoLinkPlugin />
            <RichTextPlugin
              contentEditable={
                <EditorScroller>
                  <EditorWrapper ref={onRef}>
                    <ContentEditable className={'ContentEditable__root'} />
                  </EditorWrapper>
                </EditorScroller>
              }
              placeholder={placeholderElement}
              ErrorBoundary={null}
            />
            <LinkPlugin />
            <ClickableLinkPlugin />
            {floatingAnchorElem && (
              <FloatingLinkEditorPlugin anchorElem={floatingAnchorElem} />
            )}
          </EditorContainer>
          <OnChangePlugin onChange={onChange} />
        </LexicalComposer>
      )
    }
// plugins/onChange.tsx
interface onChangePluginProps {
  onChange: (...event: any[]) => void
}

export default function OnChangePlugin({
  onChange,
}: onChangePluginProps): JSX.Element {
  return (
    <LexicalOnchangePlugin
      onChange={(editorState: EditorState, editor: LexicalEditor) => {
        editorState.read(() => {
          const htmlString = $generateHtmlFromNodes(editor, null)
          onChange({ content: htmlString, editorState })
        })
      }}
    />
  )
}

0
投票

创建一个插件来监视编辑器的更改并更新手动注册的一个或多个字段的值,如下所示:

export default function ReactHookFormPlugin({ name, register, setValue }) {
  const [editor] = useLexicalComposerContext();

  register(`${name}EditorStateJSONString`);
  register(`${name}EditorStatePlainText`);

  useEffect(
    () =>
      editor.registerUpdateListener(({ editorState }) => {
        editorState.read(() => {
          const editorStateJSONString = JSON.stringify(editorState.toJSON());

          // Get plain text
          const parsedEditorState = editor.parseEditorState(
            editorStateJSONString
          );
          const editorStateTextString = parsedEditorState.read(() =>
            $getRoot().getTextContent()
          );

          setValue(`${name}EditorStateJSONString`, editorStateJSONString);
          setValue(`${name}EditorStatePlainText`, editorStateTextString);
        });
      }),
    [editor, name, setValue]
  );
}

其中

name
类似于您传递给
input
的名称值,而
register
setValue
来自您以反应钩子形式调用的
useForm()

参考资料:

  1. 如何在对象中注册字段而不实际将其渲染为输入
  2. 状态更新监听器
  3. 如何将editorState的输出转换为纯文本?
© www.soinside.com 2019 - 2024. All rights reserved.