/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ import { useMemo, useState, useTransition } from 'react'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { ListPlugin } from '@lexical/react/LexicalListPlugin'; import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin'; import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import { HeadingNode, QuoteNode } from '@lexical/rich-text'; import { ListItemNode, ListNode } from '@lexical/list'; import { CodeHighlightNode, CodeNode } from '@lexical/code'; import VariableLabelPickerPlugin from './plugins/VariableLabelPickerPlugin'; import ListDisplayFixPlugin from './plugins/ListDisplayFixPlugin'; import { Box, Flex } from '@chakra-ui/react'; import styles from './index.module.scss'; import VariablePlugin from './plugins/VariablePlugin'; import { VariableNode } from './plugins/VariablePlugin/node'; import type { EditorState, LexicalEditor } from 'lexical'; import OnBlurPlugin from './plugins/OnBlurPlugin'; import type { FormPropsType } from './type'; import { type EditorVariableLabelPickerType, type EditorVariablePickerType } from './type'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import FocusPlugin from './plugins/FocusPlugin'; import { textToEditorState } from './utils'; import { MaxLengthPlugin } from './plugins/MaxLengthPlugin'; import { VariableLabelNode } from './plugins/VariableLabelPlugin/node'; import VariableLabelPlugin from './plugins/VariableLabelPlugin'; import { useDeepCompareEffect } from 'ahooks'; import VariablePickerPlugin from './plugins/VariablePickerPlugin'; import MarkdownPlugin from './plugins/MarkdownPlugin'; import MyIcon from '../../Icon'; import TabToSpacesPlugin from './plugins/TabToSpacesPlugin'; import ListExitPlugin from './plugins/ListExitPlugin'; const Placeholder = ({ children }: { children: React.ReactNode }) => ( {children} ); export type EditorProps = { isRichText?: boolean; variables?: EditorVariablePickerType[]; variableLabels?: EditorVariableLabelPickerType[]; value?: string; showOpenModal?: boolean; minH?: number; maxH?: number; maxLength?: number; placeholder?: string; isInvalid?: boolean; ExtensionPopover?: ((e: { onChangeText: (text: string) => void; iconButtonStyle: Record; }) => React.ReactNode)[]; }; export default function Editor({ isRichText = false, minH = 200, maxH = 400, maxLength, showOpenModal = true, onOpenModal, variables = [], variableLabels = [], onChange, onChangeText, onBlur, value, placeholder = '', bg = 'white', isInvalid, ExtensionPopover }: EditorProps & FormPropsType & { onOpenModal?: () => void; onChange: (editorState: EditorState, editor: LexicalEditor) => void; onChangeText?: ((text: string) => void) | undefined; onBlur?: (editor: LexicalEditor) => void; }) { const [key, setKey] = useState(getNanoid(6)); const [_, startSts] = useTransition(); const [focus, setFocus] = useState(false); const [scrollHeight, setScrollHeight] = useState(0); const initialConfig = { namespace: isRichText ? 'richPromptEditor' : 'promptEditor', nodes: [ VariableNode, VariableLabelNode, HeadingNode, ListNode, ListItemNode, QuoteNode, CodeNode, CodeHighlightNode ], editorState: textToEditorState(value), onError: (error: Error) => { throw error; } }; useDeepCompareEffect(() => { if (focus) return; setKey(getNanoid(6)); }, [value, variables, variableLabels]); const showFullScreenIcon = useMemo(() => { return showOpenModal && scrollHeight > maxH; }, [showOpenModal, scrollHeight, maxH]); const iconButtonStyle = useMemo( () => ({ position: 'absolute' as const, bottom: 1, right: showFullScreenIcon ? '34px' : 2, zIndex: 10, cursor: 'pointer', borderRadius: '6px', background: 'rgba(255, 255, 255, 0.01)', backdropFilter: 'blur(6.6666669845581055px)', alignItems: 'center', justifyContent: 'center', w: 6, h: 6 }), [showFullScreenIcon] ); return ( {/* Text type */} {isRichText ? ( setFocus(true)} onBlur={() => setFocus(false)} /> } placeholder={{placeholder}} ErrorBoundary={LexicalErrorBoundary} /> ) : ( } placeholder={{placeholder}} ErrorBoundary={LexicalErrorBoundary} /> )} {/* Basic Plugin */} <> {variableLabels.length > 0 && ( <> )} {variableLabels.length > 0 && } { const rootElement = editor.getRootElement(); setScrollHeight(rootElement?.scrollHeight || 0); startSts(() => { onChange?.(editorState, editor); }); }} /> {isRichText && ( <> {/* */} {/* */} )} {onChangeText && ExtensionPopover?.map((Item, index) => ( ))} {showFullScreenIcon && ( )} ); }