2020-11-20 09:44:50 +00:00
|
|
|
import EditorJS, { LogLevels, OutputData } from "@editorjs/editorjs";
|
2021-05-14 08:15:15 +00:00
|
|
|
import { FormControl, FormHelperText, InputLabel } from "@material-ui/core";
|
2022-01-10 12:34:17 +00:00
|
|
|
import { PromiseQueue } from "@saleor/misc";
|
2019-08-09 11:14:35 +00:00
|
|
|
import classNames from "classnames";
|
2019-08-09 10:26:22 +00:00
|
|
|
import React from "react";
|
2019-06-19 14:40:52 +00:00
|
|
|
|
2020-11-05 16:30:38 +00:00
|
|
|
import { RichTextEditorContentProps, tools } from "./RichTextEditorContent";
|
|
|
|
import useStyles from "./styles";
|
|
|
|
|
2020-11-03 11:35:36 +00:00
|
|
|
export type RichTextEditorChange = (data: OutputData) => void;
|
2020-11-05 16:30:38 +00:00
|
|
|
export interface RichTextEditorProps extends RichTextEditorContentProps {
|
2019-06-19 14:40:52 +00:00
|
|
|
disabled: boolean;
|
|
|
|
error: boolean;
|
|
|
|
helperText: string;
|
|
|
|
label: string;
|
|
|
|
name: string;
|
2020-11-03 11:35:36 +00:00
|
|
|
onChange: RichTextEditorChange;
|
2019-06-19 14:40:52 +00:00
|
|
|
}
|
|
|
|
|
2020-10-28 15:26:30 +00:00
|
|
|
const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
2020-11-03 11:35:36 +00:00
|
|
|
data,
|
2020-11-05 13:56:29 +00:00
|
|
|
disabled,
|
2020-10-28 15:26:30 +00:00
|
|
|
error,
|
|
|
|
helperText,
|
2020-11-03 11:35:36 +00:00
|
|
|
label,
|
2020-11-05 13:56:29 +00:00
|
|
|
name,
|
2020-11-03 13:53:17 +00:00
|
|
|
onChange,
|
|
|
|
onReady
|
2020-10-28 15:26:30 +00:00
|
|
|
}) => {
|
|
|
|
const classes = useStyles({});
|
2019-10-30 14:34:24 +00:00
|
|
|
|
2020-10-28 15:26:30 +00:00
|
|
|
const [isFocused, setFocus] = React.useState(false);
|
|
|
|
const editor = React.useRef<EditorJS>();
|
|
|
|
const editorContainer = React.useRef<HTMLDivElement>();
|
2022-01-10 12:34:17 +00:00
|
|
|
const togglePromiseQueue = React.useRef(PromiseQueue()); // used to await subsequent toggle invocations
|
2021-04-16 12:33:14 +00:00
|
|
|
const initialMount = React.useRef(true);
|
2021-01-21 08:54:53 +00:00
|
|
|
|
2020-11-03 11:35:36 +00:00
|
|
|
React.useEffect(
|
|
|
|
() => {
|
2022-02-02 22:12:58 +00:00
|
|
|
if (data !== undefined && !editor.current) {
|
|
|
|
const editorjs = new EditorJS({
|
2020-11-03 11:35:36 +00:00
|
|
|
data,
|
|
|
|
holder: editorContainer.current,
|
2020-11-20 09:44:50 +00:00
|
|
|
logLevel: "ERROR" as LogLevels,
|
2020-11-03 11:35:36 +00:00
|
|
|
onChange: async api => {
|
2021-07-01 11:23:36 +00:00
|
|
|
const savedData = await api.saver.save();
|
|
|
|
onChange(savedData);
|
2020-10-28 15:26:30 +00:00
|
|
|
},
|
2020-11-05 16:32:55 +00:00
|
|
|
onReady: () => {
|
2021-01-21 08:54:53 +00:00
|
|
|
// FIXME: This throws an error and is not working
|
|
|
|
// const undo = new Undo({ editor });
|
|
|
|
// undo.initialize(data);
|
|
|
|
|
2022-02-02 22:12:58 +00:00
|
|
|
editor.current = editorjs;
|
|
|
|
|
2020-11-05 16:32:55 +00:00
|
|
|
if (onReady) {
|
|
|
|
onReady();
|
|
|
|
}
|
|
|
|
},
|
2020-11-05 13:56:29 +00:00
|
|
|
readOnly: disabled,
|
2020-11-05 16:30:38 +00:00
|
|
|
tools
|
2020-11-03 11:35:36 +00:00
|
|
|
});
|
2020-10-28 15:26:30 +00:00
|
|
|
}
|
2020-11-03 11:35:36 +00:00
|
|
|
|
2022-02-02 22:12:58 +00:00
|
|
|
return () => {
|
|
|
|
if (editor.current) {
|
|
|
|
editor.current.destroy();
|
|
|
|
}
|
|
|
|
editor.current = null;
|
|
|
|
};
|
2020-11-03 11:35:36 +00:00
|
|
|
},
|
|
|
|
// Rerender editor only if changed from undefined to defined state
|
|
|
|
[data === undefined]
|
|
|
|
);
|
2021-01-21 08:54:53 +00:00
|
|
|
|
2020-11-05 13:56:29 +00:00
|
|
|
React.useEffect(() => {
|
2021-01-21 08:54:53 +00:00
|
|
|
const toggle = async () => {
|
2021-04-16 12:33:14 +00:00
|
|
|
if (!editor.current) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await editor.current.isReady;
|
2021-01-21 08:54:53 +00:00
|
|
|
if (editor.current?.readOnly) {
|
|
|
|
// readOnly.toggle() by itself does not enqueue the events and will result in a broken output if invocations overlap
|
|
|
|
// Remove this logic when this is fixed in EditorJS
|
2022-01-10 12:34:17 +00:00
|
|
|
togglePromiseQueue.current.add(() =>
|
|
|
|
editor.current.readOnly.toggle(disabled)
|
|
|
|
);
|
2021-04-16 12:33:14 +00:00
|
|
|
|
|
|
|
// Switching to readOnly with empty blocks present causes the editor to freeze
|
|
|
|
// Remove this logic when this is fixed in EditorJS
|
|
|
|
if (!disabled && !data?.blocks?.length) {
|
2022-01-10 12:34:17 +00:00
|
|
|
await togglePromiseQueue.current.queue;
|
2021-04-16 12:33:14 +00:00
|
|
|
editor.current.clear();
|
|
|
|
}
|
2021-01-21 08:54:53 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-04-16 12:33:14 +00:00
|
|
|
if (!initialMount.current) {
|
|
|
|
toggle();
|
|
|
|
} else {
|
|
|
|
initialMount.current = false;
|
|
|
|
}
|
2020-11-05 13:56:29 +00:00
|
|
|
}, [disabled]);
|
2019-10-30 14:34:24 +00:00
|
|
|
|
|
|
|
return (
|
2020-11-05 13:56:29 +00:00
|
|
|
<FormControl
|
|
|
|
data-test="richTextEditor"
|
|
|
|
data-test-id={name}
|
|
|
|
disabled={disabled}
|
|
|
|
error={error}
|
|
|
|
fullWidth
|
|
|
|
variant="outlined"
|
|
|
|
>
|
|
|
|
<InputLabel focused={true} shrink={true}>
|
|
|
|
{label}
|
|
|
|
</InputLabel>
|
2020-10-28 15:26:30 +00:00
|
|
|
<div
|
2020-11-05 16:30:38 +00:00
|
|
|
className={classNames(classes.editor, classes.root, {
|
2020-11-05 13:56:29 +00:00
|
|
|
[classes.rootActive]: isFocused,
|
|
|
|
[classes.rootDisabled]: disabled,
|
|
|
|
[classes.rootError]: error
|
2020-10-28 15:26:30 +00:00
|
|
|
})}
|
|
|
|
ref={editorContainer}
|
|
|
|
onFocus={() => setFocus(true)}
|
|
|
|
onBlur={() => setFocus(false)}
|
2020-11-05 13:56:29 +00:00
|
|
|
/>
|
|
|
|
<FormHelperText>{helperText}</FormHelperText>
|
|
|
|
</FormControl>
|
2019-10-30 14:34:24 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2019-06-19 14:40:52 +00:00
|
|
|
RichTextEditor.displayName = "RichTextEditor";
|
|
|
|
export default RichTextEditor;
|