ec286b3480
新增可复用的 RichTextInput Lexical 输入外壳。 让首页输入与资源引用输入共享编辑器、回车提交和禁用状态。 保留首页附件与图片粘贴节点能力,并移除 DirectProject 草稿 legacy fallback。 统一聊天草稿 canonical content 状态并更新资源引用测试。
103 lines
2.7 KiB
TypeScript
103 lines
2.7 KiB
TypeScript
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
|
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
|
|
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
|
|
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
|
import {
|
|
COMMAND_PRIORITY_HIGH,
|
|
type EditorState,
|
|
KEY_ENTER_COMMAND,
|
|
type Klass,
|
|
type LexicalNode,
|
|
} from 'lexical';
|
|
import type { ReactElement, ReactNode, Ref } from 'react';
|
|
import { useEffect } from 'react';
|
|
|
|
type RichTextInputProps = {
|
|
namespace: string;
|
|
nodes: Klass<LexicalNode>[];
|
|
initialEditorState?: EditorState | null;
|
|
contentEditable?: ReactElement<typeof ContentEditable>;
|
|
placeholder?: ReactElement;
|
|
containerClassName?: string;
|
|
containerRef?: Ref<HTMLDivElement>;
|
|
disabled?: boolean;
|
|
onChange?: (editorState: EditorState) => void;
|
|
onEnter?: () => void;
|
|
children?: ReactNode;
|
|
};
|
|
|
|
function SubmitOnEnter({ onEnter }: { onEnter?: () => void }) {
|
|
const [editor] = useLexicalComposerContext();
|
|
|
|
useEffect(() => {
|
|
if (!onEnter) return undefined;
|
|
return editor.registerCommand(
|
|
KEY_ENTER_COMMAND,
|
|
(event) => {
|
|
if (!event || event.shiftKey || event.isComposing) return false;
|
|
event.preventDefault();
|
|
onEnter();
|
|
return true;
|
|
},
|
|
COMMAND_PRIORITY_HIGH,
|
|
);
|
|
}, [editor, onEnter]);
|
|
|
|
return null;
|
|
}
|
|
|
|
function SetEditorEditable({ disabled }: { disabled: boolean }) {
|
|
const [editor] = useLexicalComposerContext();
|
|
|
|
useEffect(() => {
|
|
editor.setEditable(!disabled);
|
|
}, [disabled, editor]);
|
|
|
|
return null;
|
|
}
|
|
|
|
export default function RichTextInput({
|
|
namespace,
|
|
nodes,
|
|
initialEditorState,
|
|
contentEditable = <ContentEditable />,
|
|
placeholder,
|
|
containerClassName,
|
|
containerRef,
|
|
disabled = false,
|
|
onChange,
|
|
onEnter,
|
|
children,
|
|
}: RichTextInputProps) {
|
|
return (
|
|
<LexicalComposer
|
|
initialConfig={{
|
|
namespace,
|
|
nodes,
|
|
editorState: initialEditorState ?? undefined,
|
|
onError: (error) => {
|
|
throw error;
|
|
},
|
|
}}
|
|
>
|
|
<div
|
|
ref={containerRef}
|
|
className={containerClassName}
|
|
data-disabled={disabled ? 'true' : undefined}
|
|
>
|
|
<RichTextPlugin
|
|
contentEditable={contentEditable}
|
|
placeholder={placeholder}
|
|
ErrorBoundary={LexicalErrorBoundary}
|
|
/>
|
|
{children}
|
|
<SetEditorEditable disabled={disabled} />
|
|
<SubmitOnEnter onEnter={onEnter} />
|
|
{onChange ? <OnChangePlugin onChange={onChange} /> : null}
|
|
</div>
|
|
</LexicalComposer>
|
|
);
|
|
}
|