saleor-dashboard/src/components/RichTextEditor/RichTextEditor.tsx

125 lines
3.4 KiB
TypeScript
Raw Normal View History

2020-11-20 09:44:50 +00:00
import EditorJS, { LogLevels, OutputData } from "@editorjs/editorjs";
import { FormControl, FormHelperText, InputLabel } from "@material-ui/core";
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";
import { clean } from "./utils";
2020-11-05 16:30:38 +00:00
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>();
const togglePromiseQueue = React.useRef(PromiseQueue()); // used to await subsequent toggle invocations
2020-11-03 11:35:36 +00:00
React.useEffect(
() => {
if (data !== undefined && !editor.current) {
editor.current = 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 => {
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: () => {
// FIXME: This throws an error and is not working
// const undo = new Undo({ editor });
// undo.initialize(data);
2020-11-05 16:32:55 +00:00
if (onReady) {
onReady();
}
},
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
return () => {
clean(editor.current);
editor.current = null;
};
2020-11-03 11:35:36 +00:00
},
// Rerender editor only if changed from undefined to defined state
[data === undefined]
);
2020-11-05 13:56:29 +00:00
React.useEffect(() => {
const toggle = async () => {
if (!editor.current) {
return;
}
await editor.current.isReady;
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
togglePromiseQueue.current.add(() =>
editor.current.readOnly.toggle(disabled)
);
// 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) {
await togglePromiseQueue.current.queue;
editor.current.clear();
}
}
};
toggle();
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-id={"rich-text-editor-" + name}
2020-11-05 13:56:29 +00:00
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;