arch-editor
Version:
Rich text editor with a high degree of customization.
178 lines (155 loc) • 4.99 kB
JavaScript
import 'katex/dist/katex.min.css';
import React, { useCallback, useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames/bind';
import { BlockMath } from 'react-katex';
import { removeBlockEntity, updateBlockEntityData } from '@/utils/draftUtils';
import { useToolbar, EditToolbar } from '@/utils/atomic';
import Tooltip from '@/components/Tooltip';
import Icon from '@/components/Icon';
import Modal from '@/components/Modal';
import LaTexDoc from '@/components/LaTexDoc';
import styles from './AtomicFormula.less';
const cx = classNames.bind(styles);
export default function AtomicFormula(props) {
// props
const { block, data, readOnly, editorState, setEditorState, editing, setEditing } = props;
// ref
const inputRef = useRef(null);
const atomicFormula = useRef(null);
// state
const [raw, setRaw] = useState('');
const [modalOpen, setModalOpen] = useState(false);
// effect
useEffect(() => {
// initial status
let timer;
if (data.initial) {
setEditing(true, () => {
timer = setTimeout(() => {
if (atomicFormula.current) atomicFormula.current.scrollIntoView();
}, 0);
});
}
return () => {
if (timer) clearTimeout(timer);
};
}, [data, setEditing]);
useEffect(() => {
// 编辑状态更改:手动初始化数据
setRaw(data.raw);
}, [data, editing]);
// handler
const execRemoveAction = useCallback(() => {
const newEditorState = removeBlockEntity(block, editorState);
setEditorState(newEditorState);
}, [block, editorState, setEditorState]);
const execSubmitAction = useCallback(() => {
const newEditorState = updateBlockEntityData(block, editorState, { raw, initial: false });
setEditorState(newEditorState);
}, [block, editorState, raw, setEditorState]);
const handleEdit = useCallback(() => {
setEditing(true);
}, [setEditing]);
const handleSubmit = useCallback(() => {
if (!raw) return;
setEditing(false, () => {
execSubmitAction();
});
}, [execSubmitAction, raw, setEditing]);
const handleDelete = useCallback(() => {
if (editing) {
setEditing(false, () => {
execRemoveAction();
});
return;
}
execRemoveAction();
}, [editing, execRemoveAction, setEditing]);
const handleCancel = useCallback(() => {
setEditing(false);
}, [setEditing]);
const handleModalClose = useCallback(() => {
setModalOpen(false);
}, []);
const handleMathError = useCallback(
(err) => <div className={styles.tipInfo}>{err.message}</div>,
[],
);
const handleInputChange = (e) => {
setRaw(e.target.value);
};
const handleKeyDown = (e) => {
if (e.ctrlKey && e.keyCode === 13) {
// Ctrl + Enter
handleSubmit();
}
if (e.keyCode === 27) {
// Esc
handleCancel();
}
};
const toolbar = useToolbar({
editing: false,
onEdit: handleEdit,
onDelete: handleDelete,
});
const core = raw ? (
<BlockMath math={String.raw`${raw}`} renderError={handleMathError} />
) : (
<span className={styles.placeholder}>
公式展示区
{!editing && '(无公式)'}
</span>
);
if (readOnly) {
// 只读模式无法操作,只负责静态渲染
return <div className={styles.formula}>{core}</div>;
}
if (editing) {
return (
<div className={styles.formulaContainer} ref={atomicFormula}>
<div className={cx('formula', { active: editing })}>
{core}
<textarea
className={styles.inputTex}
placeholder="请输入Tex表达式(Ctrl+Enter 提交,Esc 返回)"
ref={inputRef}
value={raw}
onChange={handleInputChange}
autoFocus
rows={3}
onKeyDown={handleKeyDown}
/>
<div className={styles.adviseInfo}>
<Icon name="question" style={{ fontSize: 15, marginRight: 3 }} />
<span>
您还不了解Tex表达式?
<button type="button" onClick={() => setModalOpen(true)}>
查看规则
</button>
</span>
</div>
</div>
<EditToolbar onCancel={handleCancel} onOk={handleSubmit} onDelete={handleDelete} />
<Modal isOpen={modalOpen} onRequestClose={handleModalClose}>
<LaTexDoc />
</Modal>
</div>
);
}
return (
<Tooltip content={toolbar}>
<div className={styles.formula}>{core}</div>
</Tooltip>
);
}
AtomicFormula.propTypes = {
block: PropTypes.object,
data: PropTypes.any,
readOnly: PropTypes.bool,
editorState: PropTypes.object,
setEditorState: PropTypes.func,
editing: PropTypes.bool,
setEditing: PropTypes.func,
};