2020-10-28 15:26:30 +00:00
|
|
|
import EditorJS, { OutputData } from "@editorjs/editorjs";
|
|
|
|
import Header from "@editorjs/header";
|
|
|
|
import List from "@editorjs/list";
|
|
|
|
import Quote from "@editorjs/quote";
|
2020-11-05 13:56:29 +00:00
|
|
|
import FormControl from "@material-ui/core/FormControl";
|
|
|
|
import FormHelperText from "@material-ui/core/FormHelperText";
|
|
|
|
import InputLabel from "@material-ui/core/InputLabel";
|
2019-10-30 14:34:24 +00:00
|
|
|
import { makeStyles } from "@material-ui/core/styles";
|
2019-06-19 14:40:52 +00:00
|
|
|
import { fade } from "@material-ui/core/styles/colorManipulator";
|
2020-10-28 15:26:30 +00:00
|
|
|
import strikethroughIcon from "@saleor/icons/StrikethroughIcon";
|
2019-08-09 11:14:35 +00:00
|
|
|
import classNames from "classnames";
|
2020-10-28 15:30:44 +00:00
|
|
|
import createGenericInlineTool from "editorjs-inline-tool";
|
2019-08-09 10:26:22 +00:00
|
|
|
import React from "react";
|
2019-06-19 14:40:52 +00:00
|
|
|
|
2020-11-03 11:35:36 +00:00
|
|
|
export type RichTextEditorChange = (data: OutputData) => void;
|
2019-06-19 14:40:52 +00:00
|
|
|
export interface RichTextEditorProps {
|
2020-11-03 11:35:36 +00:00
|
|
|
data: OutputData;
|
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;
|
2020-11-03 13:53:17 +00:00
|
|
|
onReady?: () => void;
|
2019-06-19 14:40:52 +00:00
|
|
|
}
|
|
|
|
|
2020-11-04 12:56:04 +00:00
|
|
|
// TODO: improve dark mode
|
2019-12-03 15:28:40 +00:00
|
|
|
const useStyles = makeStyles(
|
2020-02-03 11:16:07 +00:00
|
|
|
theme => {
|
2020-10-28 15:26:30 +00:00
|
|
|
const hover = {
|
|
|
|
"&:hover": {
|
|
|
|
background: fade(theme.palette.primary.main, 0.1)
|
|
|
|
}
|
2020-02-03 11:16:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
root: {
|
2020-10-28 15:26:30 +00:00
|
|
|
"& .cdx-quote__text": {
|
|
|
|
minHeight: 24
|
2019-12-03 15:28:40 +00:00
|
|
|
},
|
2020-11-05 14:10:46 +00:00
|
|
|
"& .ce-block--selected .ce-block__content": {
|
2020-11-05 14:31:00 +00:00
|
|
|
background: `${fade(theme.palette.primary.main, 0.2)} !important`
|
2020-11-05 14:10:46 +00:00
|
|
|
},
|
2020-10-29 12:14:05 +00:00
|
|
|
"& .ce-block__content": {
|
|
|
|
margin: 0,
|
|
|
|
maxWidth: "unset"
|
|
|
|
},
|
2020-10-28 15:26:30 +00:00
|
|
|
"& .ce-conversion-tool": {
|
|
|
|
...hover
|
2019-10-30 14:34:24 +00:00
|
|
|
},
|
2020-10-28 15:26:30 +00:00
|
|
|
"& .ce-conversion-tool--focused": {
|
|
|
|
background: `${fade(theme.palette.primary.main, 0.1)} !important`
|
|
|
|
},
|
2020-11-05 14:10:46 +00:00
|
|
|
"& .ce-conversion-tool__icon": {
|
|
|
|
background: "none"
|
|
|
|
},
|
|
|
|
"& .ce-conversion-toolbar": {
|
|
|
|
background: theme.palette.background.paper
|
|
|
|
},
|
2020-11-04 12:48:58 +00:00
|
|
|
"& .ce-header": {
|
|
|
|
marginBottom: 0,
|
|
|
|
paddingBottom: theme.spacing(1)
|
|
|
|
},
|
2020-10-28 15:26:30 +00:00
|
|
|
"& .ce-inline-tool": {
|
|
|
|
...hover,
|
2020-11-05 14:10:46 +00:00
|
|
|
color: theme.palette.text.primary,
|
2020-10-28 15:26:30 +00:00
|
|
|
height: 32,
|
|
|
|
transition: theme.transitions.duration.short + "ms",
|
|
|
|
width: 32
|
|
|
|
},
|
2020-11-05 14:10:46 +00:00
|
|
|
"& .ce-inline-toolbar": {
|
|
|
|
"& input": {
|
|
|
|
background: "none"
|
|
|
|
},
|
|
|
|
background: theme.palette.background.paper,
|
|
|
|
color: theme.palette.text.primary
|
|
|
|
},
|
2020-10-28 15:26:30 +00:00
|
|
|
"& .ce-inline-toolbar__dropdown": {
|
|
|
|
...hover,
|
|
|
|
height: 32,
|
|
|
|
marginRight: 0
|
|
|
|
},
|
|
|
|
"& .ce-inline-toolbar__toggler-and-button-wrapper": {
|
|
|
|
paddingRight: 0
|
|
|
|
},
|
2020-11-03 16:17:37 +00:00
|
|
|
"& .ce-toolbar__actions": {
|
|
|
|
right: 0,
|
|
|
|
top: 0
|
|
|
|
},
|
|
|
|
"& .ce-toolbar__content": {
|
|
|
|
maxWidth: "unset"
|
|
|
|
},
|
|
|
|
"& .ce-toolbar__plus": {
|
|
|
|
left: -9
|
|
|
|
},
|
|
|
|
"& .ce-toolbox.ce-toolbox--opened": {
|
|
|
|
left: 16
|
|
|
|
},
|
2020-10-28 15:26:30 +00:00
|
|
|
"& .codex-editor__redactor": {
|
|
|
|
marginRight: `${theme.spacing(4)}px !important`,
|
|
|
|
paddingBottom: "0 !important"
|
|
|
|
},
|
|
|
|
"& a": {
|
|
|
|
color: theme.palette.primary.light
|
|
|
|
},
|
2020-11-05 13:56:29 +00:00
|
|
|
"&:not($rootDisabled):hover": {
|
2020-10-28 15:26:30 +00:00
|
|
|
borderColor: theme.palette.primary.main
|
|
|
|
},
|
2020-10-29 12:14:05 +00:00
|
|
|
border: `1px solid ${fade(theme.palette.text.secondary, 0.4)}`,
|
|
|
|
borderRadius: 4,
|
2020-10-28 15:26:30 +00:00
|
|
|
boxShadow: `inset 0 0 0 0 ${theme.palette.primary.main}`,
|
2020-11-03 13:53:17 +00:00
|
|
|
fontSize: theme.typography.body1.fontSize,
|
2020-11-05 13:56:29 +00:00
|
|
|
minHeight: 56,
|
2020-10-28 15:26:30 +00:00
|
|
|
padding: theme.spacing(3, 2),
|
2020-11-03 13:53:17 +00:00
|
|
|
paddingBottom: theme.spacing(),
|
|
|
|
paddingLeft: 10,
|
2020-10-29 12:14:05 +00:00
|
|
|
position: "relative",
|
2020-10-28 15:26:30 +00:00
|
|
|
transition: theme.transitions.duration.short + "ms"
|
2020-02-03 11:16:07 +00:00
|
|
|
},
|
2020-10-28 15:26:30 +00:00
|
|
|
rootActive: {
|
|
|
|
boxShadow: `inset 0px 0px 0 2px ${theme.palette.primary.main}`
|
2020-11-05 13:56:29 +00:00
|
|
|
},
|
|
|
|
rootDisabled: {
|
|
|
|
...theme.overrides.MuiOutlinedInput.root["&$disabled"]["& fieldset"]
|
|
|
|
},
|
|
|
|
rootError: {
|
|
|
|
borderColor: theme.palette.error.main
|
2019-06-19 14:40:52 +00:00
|
|
|
}
|
2020-02-03 11:16:07 +00:00
|
|
|
};
|
|
|
|
},
|
2019-12-03 15:28:40 +00:00
|
|
|
{ name: "RichTextEditor" }
|
|
|
|
);
|
2019-08-09 11:14:35 +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>();
|
2020-11-03 11:35:36 +00:00
|
|
|
React.useEffect(
|
|
|
|
() => {
|
|
|
|
if (data) {
|
|
|
|
editor.current = new EditorJS({
|
|
|
|
data,
|
|
|
|
holder: editorContainer.current,
|
|
|
|
onChange: async api => {
|
|
|
|
const savedData = await api.saver.save();
|
|
|
|
onChange(savedData);
|
2020-10-28 15:26:30 +00:00
|
|
|
},
|
2020-11-03 13:53:17 +00:00
|
|
|
onReady,
|
2020-11-05 13:56:29 +00:00
|
|
|
readOnly: disabled,
|
2020-11-03 11:35:36 +00:00
|
|
|
tools: {
|
|
|
|
header: {
|
|
|
|
class: Header,
|
|
|
|
config: {
|
|
|
|
defaultLevel: 1,
|
|
|
|
levels: [1, 2, 3]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
list: List,
|
|
|
|
quote: Quote,
|
|
|
|
strikethrough: createGenericInlineTool({
|
|
|
|
sanitize: {
|
2020-11-05 14:16:39 +00:00
|
|
|
s: {}
|
2020-11-03 11:35:36 +00:00
|
|
|
},
|
|
|
|
shortcut: "CMD+S",
|
|
|
|
tagName: "s",
|
|
|
|
toolboxIcon: strikethroughIcon
|
|
|
|
})
|
|
|
|
}
|
|
|
|
});
|
2020-10-28 15:26:30 +00:00
|
|
|
}
|
2020-11-03 11:35:36 +00:00
|
|
|
|
|
|
|
return editor.current?.destroy;
|
|
|
|
},
|
|
|
|
// Rerender editor only if changed from undefined to defined state
|
|
|
|
[data === undefined]
|
|
|
|
);
|
|
|
|
React.useEffect(() => editor.current?.destroy, []);
|
2020-11-05 13:56:29 +00:00
|
|
|
React.useEffect(() => {
|
|
|
|
if (editor.current?.readOnly) {
|
|
|
|
editor.current.readOnly.toggle(disabled);
|
|
|
|
}
|
|
|
|
}, [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
|
|
|
|
className={classNames(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;
|