import React, {useEffect, useMemo, useRef, useState} from 'react' import {withReact, Slate, ReactEditor} from '5e-slate-react' import {createEditor, Descendant, Range, Editor, Transforms} from 'slate' import {withHistory} from 'slate-history' import {EditorProvider} from './editor_context' import {EditorType} from '../global/interface' import {withMentions} from '../plugins/withMentions' import MentionsItem from '../element/mentions/portal_item' import {getInitialValue, initialValue} from '../global/utils' import EditorWrapper from './editor' import {initParagraph} from '../global/const' import {insertMention} from '../global/tool' import {withCompletion} from '../plugins/withCompletion' export interface OperateNodes { type: 'insert' | 'empty' nodes: any[] | any initData?: InitData[] isReply?: boolean } interface InitData { type: string children: {text: string}[] | InitData[] character?: T } // TODO: any 类型修改 interface Props { // readonly 编辑器是否 readonly: boolean // value data?: InitData[] // selectArr 可以被选中的人 selectArr?: any[] // insetNodes (外部插入节点: 重置 + 插入) operateNodes?: OperateNodes // className classNames?: string // mentionPosition mentionPosition?: { top?: boolean right?: boolean } // isHideMention 隐藏mention isHideMention?: boolean // changeValue changeValue?(s: any): void // search 开始搜索 onSearch?(s: string): void onResetValue?(cb: any): void // 子组件传递父组件的回调函数 onCallBack?(...s: any): void } const BaseEditor = (baseProps: Partial = {onSearch: () => void 0}) => { const { readonly, changeValue, data, operateNodes, selectArr, mentionPosition, classNames, isHideMention, onCallBack = () => void 0, } = baseProps const [value, setValue] = useState(initialValue('')) const [target, setTarget] = useState() const [index, setIndex] = useState(0) const [search, setSearch] = useState('') const timer = useRef(null) // 初始化默认值 useEffect(() => { if (!data?.length) return setValue(data) // TODO: 默认进入时设置好内容后 -> 默认让其失去焦点 }, [data]) // 失去焦点 const onBlur = () => ReactEditor.blur(editor) // 聚焦 const onFocus = () => ReactEditor.focus(editor) const reSetValue = () => { Transforms.select(editor, Editor.start(editor, [])) setValue(initParagraph) } useEffect(() => { // eslint-disable-next-line no-unused-expressions baseProps?.onResetValue?.(reSetValue) }, []) // editor 编辑器(相关) const editor = useMemo( () => withCompletion(withMentions(withHistory(withReact(createEditor() as ReactEditor)))), [] ) // 组件函数直接抛出外层函数进行执行 useEffect(() => { onCallBack({ onBlur: () => ReactEditor.blur(editor), onFocus: () => ReactEditor.focus(editor), insertData: insertInfo(editor), }) }, [editor]) // 外部插入数据时 触发的逻辑 useEffect(() => { if (operateNodes?.type === 'insert') { setValue(operateNodes.initData || []) timer.current = setTimeout(() => { ReactEditor.focus(editor) const mention: any = getInitialValue('回复', operateNodes) Transforms.insertNodes(editor, mention) Transforms.move(editor) clearTimeout(timer.current) }, 100) } }, [operateNodes]) // 插入数据 const insertInfo = (editors: any) => (info: any) => { ReactEditor.focus(editors) Transforms.collapse(editors, {edge: 'anchor'}) // console.error(editors.selection, '=') Transforms.insertNodes(editors, info, {at: editor.selection?.anchor}) Transforms.move(editors) } const changeValues = (v: any) => { if (readonly && !isHideMention) return if (JSON.stringify(v) === JSON.stringify(value)) return setValue(v) changeValue!(v) const {selection} = editor // console.error(editor.selection, '===') // 只出现@时单独处理逻辑 if (sessionStorage.getItem('isFirstMention')) { const [start] = Range.edges(selection!) const wordBefore = Editor.before(editor, start, {unit: 'offset'}) const afterRange = wordBefore && Editor.range(editor, wordBefore, start) // console.error(afterRange, 'afterRange') if (afterRange) { setTarget(afterRange) baseProps.onSearch!('') setIndex(0) sessionStorage.setItem('isFirstMention', '') } return } // @后面接文字 if (selection && Range.isCollapsed(selection)) { const [start] = Range.edges(selection) const wordBefore = Editor.before(editor, start, {unit: 'word'}) const before = wordBefore && Editor.before(editor, wordBefore) const beforeRange = before && Editor.range(editor, before, start) const beforeText = beforeRange && Editor.string(editor, beforeRange) // 支持@ 后面传入中文 const beforeMatch = beforeText && beforeText?.match(/^@(.+)$/) if (beforeMatch) { setTarget(beforeRange) setSearch(beforeMatch[1]) baseProps.onSearch!(beforeMatch[1]) setIndex(0) return } } setTarget(undefined) } // 鼠标点击事件 const onClickItem = (i: number) => { Transforms.select(editor, target!) insertMention(editor, chars[i]) Transforms.insertText(editor, ' ') ReactEditor.focus(editor) setTarget(undefined) } // 键盘输入 特殊符号时 触发相应的逻辑 const chars = selectArr?.slice(0, 10) || [] console.error(value, '----') return (
{/* 移动端屏蔽@ 逻辑 */} {target && !isHideMention && chars.length > 0 && ( )}
) } // 数据格 export default BaseEditor