From dab8064e2657942b29591e5d7831f672e7996114 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Wed, 28 Oct 2020 16:26:30 +0100
Subject: [PATCH 01/23] Use editorjs instead of draftail
---
package-lock.json | 44 +++
package.json | 6 +
.../RichTextEditor/RichTextEditor.tsx | 366 ++++++------------
src/icons/StrikethroughIcon.tsx | 12 +-
.../stories/components/RichTextEditor.tsx | 104 +----
.../stories/components/fixtures.json | 74 ++++
6 files changed, 245 insertions(+), 361 deletions(-)
create mode 100644 src/storybook/stories/components/fixtures.json
diff --git a/package-lock.json b/package-lock.json
index 73d557208..bd2e536dd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1949,6 +1949,35 @@
}
}
},
+ "@editorjs/editorjs": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.19.0.tgz",
+ "integrity": "sha512-8PUVaBZx69IrG8dNrE+FZbHSiRTR8ql8L/cmEi1mOdEdTqnOLq5Wv9dgemK00mBWEgNoavMAjtGQpItGknAa8A==",
+ "requires": {
+ "codex-notifier": "^1.1.2",
+ "codex-tooltip": "^1.0.1"
+ }
+ },
+ "@editorjs/header": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@editorjs/header/-/header-2.6.0.tgz",
+ "integrity": "sha512-1psNX/irDjJ8Bp1l7DjkYWz7IBtjVIRANk7kPkNoY2CfAeeCFYbJmMlXdqTF2WeAjYv2WMy5ey/aR5fTccgFaw=="
+ },
+ "@editorjs/image": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@editorjs/image/-/image-2.6.0.tgz",
+ "integrity": "sha512-lX4Pz9cW3gGFzlmYLRAsBXTiqUG/MRG7NK4QVU+n/VnUWPU1e791eiIpgRLHfpPj6Maaw5a+GRut90D5EdXtqg=="
+ },
+ "@editorjs/list": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@editorjs/list/-/list-1.6.0.tgz",
+ "integrity": "sha512-2oJ3Nj3lDcIKS6GcrHYHzUUabIjg7zlXTYXQWdEWXevbnM0/fq+4psyI/AYtqbaa3jN+bycPBIW4OG3zD+3d5A=="
+ },
+ "@editorjs/quote": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@editorjs/quote/-/quote-2.4.0.tgz",
+ "integrity": "sha512-IWOBWjL2ngPP63GcIAltyD9kc7OVZFma4kS+T5JRHvKKDspYsnmrxsbRmCPc+coZQzqPxXHkiOZuNMdmGX/Y3w=="
+ },
"@emotion/cache": {
"version": "10.0.19",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.19.tgz",
@@ -8778,6 +8807,16 @@
"urlgrey": "0.4.4"
}
},
+ "codex-notifier": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/codex-notifier/-/codex-notifier-1.1.2.tgz",
+ "integrity": "sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg=="
+ },
+ "codex-tooltip": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/codex-tooltip/-/codex-tooltip-1.0.1.tgz",
+ "integrity": "sha512-1xLb1NZbxguNtf02xBRhDphq/EXvMMeEbY0ievjQTHqf8UjXsD41evGk9rqcbjpl+JOjNgtwnp1OaU/X/h6fhQ=="
+ },
"coffeescript": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz",
@@ -10306,6 +10345,11 @@
"safer-buffer": "^2.1.0"
}
},
+ "editorjs-inline-tool": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/editorjs-inline-tool/-/editorjs-inline-tool-0.4.0.tgz",
+ "integrity": "sha512-Ppb4e8IFPjWuNcoNM4tg9bDSo7FgMYAlqP4UhuV5W2JoJBubV5pUcpLrFrSyGTt1HJVEpbrib134zf4wxO+7VA=="
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
diff --git a/package.json b/package.json
index 87724860c..a589c198e 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,11 @@
"npm": ">=6.11.0"
},
"dependencies": {
+ "@editorjs/editorjs": "^2.19.0",
+ "@editorjs/header": "^2.6.0",
+ "@editorjs/image": "^2.6.0",
+ "@editorjs/list": "^1.6.0",
+ "@editorjs/quote": "^2.4.0",
"@material-ui/core": "^4.5.1",
"@material-ui/icons": "^4.5.1",
"@material-ui/styles": "^4.5.2",
@@ -36,6 +41,7 @@
"draft-js": "^0.10.5",
"draftail": "^1.2.1",
"draftjs-to-html": "^0.9.1",
+ "editorjs-inline-tool": "^0.4.0",
"fast-array-diff": "^0.2.0",
"fuzzaldrin": "^2.1.0",
"graphql": "^14.4.2",
diff --git a/src/components/RichTextEditor/RichTextEditor.tsx b/src/components/RichTextEditor/RichTextEditor.tsx
index 68d8164ff..16b9a576c 100644
--- a/src/components/RichTextEditor/RichTextEditor.tsx
+++ b/src/components/RichTextEditor/RichTextEditor.tsx
@@ -1,298 +1,163 @@
+import EditorJS, { OutputData } from "@editorjs/editorjs";
+import Header from "@editorjs/header";
+import List from "@editorjs/list";
+import Quote from "@editorjs/quote";
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import Typography from "@material-ui/core/Typography";
import { CreateCSSProperties } from "@material-ui/styles/withStyles";
-import { ChangeEvent } from "@saleor/hooks/useForm";
+import { FormChange } from "@saleor/hooks/useForm";
+import strikethroughIcon from "@saleor/icons/StrikethroughIcon";
import classNames from "classnames";
-import { RawDraftContentState } from "draft-js";
-import {
- BLOCK_TYPE,
- DraftailEditor,
- ENTITY_TYPE,
- INLINE_STYLE
-} from "draftail";
-import isEqual from "lodash-es/isEqual";
+import createGenericInlineTool, {
+ ItalicInlineTool,
+ UnderlineInlineTool
+} from "editorjs-inline-tool";
import React from "react";
-import ErrorBoundary from "react-error-boundary";
-import { FormattedMessage } from "react-intl";
-
-import BoldIcon from "../../icons/BoldIcon";
-import HeaderOne from "../../icons/HeaderOne";
-import HeaderThree from "../../icons/HeaderThree";
-import HeaderTwo from "../../icons/HeaderTwo";
-import ItalicIcon from "../../icons/ItalicIcon";
-import LinkIcon from "../../icons/LinkIcon";
-import OrderedListIcon from "../../icons/OrderedListIcon";
-import QuotationIcon from "../../icons/QuotationIcon";
-import StrikethroughIcon from "../../icons/StrikethroughIcon";
-import UnorderedListIcon from "../../icons/UnorderedListIcon";
-import LinkEntity from "./LinkEntity";
-import LinkSource from "./LinkSource";
export interface RichTextEditorProps {
disabled: boolean;
error: boolean;
helperText: string;
- initial?: RawDraftContentState;
+ initial: OutputData;
label: string;
name: string;
- scroll?: boolean;
- onChange: (event: React.ChangeEvent) => void;
+ onChange: FormChange;
}
const useStyles = makeStyles(
theme => {
- const editorContainer: CreateCSSProperties = {
- border: `1px ${theme.palette.divider} solid`,
- borderRadius: 4,
- padding: "27px 12px 10px",
- position: "relative",
- transition: theme.transitions.duration.shortest + "ms"
+ const hover = {
+ "&:hover": {
+ background: fade(theme.palette.primary.main, 0.1)
+ }
};
return {
- editorContainer,
error: {
color: theme.palette.error.main
},
helperText: {
marginTop: theme.spacing(0.75)
},
- input: {
- position: "relative"
- },
label: {
- fontSize: theme.typography.caption.fontSize,
- left: 12,
+ color: theme.palette.text.secondary,
position: "absolute",
- top: 9
+ top: theme.spacing(4),
+ transition: theme.transitions.duration.short + "ms"
},
- linkIcon: {
- marginTop: 2
+ labelActive: {
+ color: theme.palette.primary.main
},
root: {
- "& .DraftEditor": {
- "&-editorContainer": {
- "& .public-DraftEditor-content": {
- lineHeight: 1.62
- },
- "& a": {
- color: theme.palette.primary.light
- },
- "&:after": {
- background: theme.palette.getContrastText(
- theme.palette.background.default
- ),
- bottom: -11,
- content: "''",
- display: "block",
- height: 2,
- left: -12,
- position: "absolute",
- transform: "scaleX(0) scaleY(0)",
- width: "calc(100% + 24px)"
- },
- position: "relative"
- },
- "&-root": {
- ...theme.typography.body1
- }
+ "& .cdx-quote__text": {
+ minHeight: 24
},
- "& .Draftail": {
- "&-Editor": {
- "&--focus": {
- boxShadow: `inset 0px 0px 0px 2px ${theme.palette.primary.main}`
- },
- "&:hover": {
- borderColor: theme.palette.primary.main
- },
- ...editorContainer
- },
- "&-Toolbar": {
- "&Button": {
- "& svg": {
- padding: 2
- },
- "&--active": {
- "&:hover": {
- background: theme.palette.primary.main
- },
- "&:not(:hover)": {
- borderRightColor: theme.palette.primary.main
- },
- background: theme.palette.primary.main
- },
- "&:focus": {
- "&:active": {
- "&:after": {
- background: fade(theme.palette.primary.main, 0.3),
- borderRadius: "100%",
- content: "''",
- display: "block",
- height: "100%",
- width: "100%"
- }
- }
- },
- "&:hover": {
- background: fade(theme.palette.primary.main, 0.3)
- },
- background: "none",
- border: "none",
- borderRight: `1px ${theme.palette.divider} solid`,
- color: theme.typography.body1.color,
- cursor: "pointer",
- display: "inline-flex",
- height: 36,
- justifyContent: "center",
- padding: theme.spacing(1) + 2,
- transition: theme.transitions.duration.short + "ms",
- width: 36
- },
- "&Group": {
- "&:last-of-type": {
- "& .Draftail-ToolbarButton": {
- "&:last-of-type": {
- border: "none"
- }
- }
- },
- display: "flex"
- },
- background: theme.palette.background.default,
- border: `1px ${theme.palette.divider} solid`,
- display: "inline-flex",
- flexWrap: "wrap",
- marginBottom: theme.spacing(),
- marginTop: 10,
- [theme.breakpoints.down(460)]: {
- width: "min-content"
- }
- },
- "&-block": {
- "&--blockquote": {
- borderLeft: `2px solid ${theme.palette.divider}`,
- margin: 0,
- padding: theme.spacing(1, 2)
- }
- }
+ "& .ce-conversion-tool": {
+ ...hover
},
- "&$error": {
- "& .Draftail": {
- "&-Editor": {
- borderColor: theme.palette.error.main
- }
- }
- }
+ "& .ce-conversion-tool--focused": {
+ background: `${fade(theme.palette.primary.main, 0.1)} !important`
+ },
+ "& .ce-inline-tool": {
+ ...hover,
+ height: 32,
+ transition: theme.transitions.duration.short + "ms",
+ width: 32
+ },
+ "& .ce-inline-toolbar__dropdown": {
+ ...hover,
+ height: 32,
+ marginRight: 0
+ },
+ "& .ce-inline-toolbar__toggler-and-button-wrapper": {
+ paddingRight: 0
+ },
+ "& .codex-editor__redactor": {
+ marginRight: `${theme.spacing(4)}px !important`,
+ paddingBottom: "0 !important"
+ },
+ "& a": {
+ color: theme.palette.primary.light
+ },
+ "&:hover": {
+ borderColor: theme.palette.primary.main
+ },
+ border: `1px solid ${theme.palette.divider}`,
+ borderRadius: 8,
+ boxShadow: `inset 0 0 0 0 ${theme.palette.primary.main}`,
+ padding: theme.spacing(3, 2),
+ transition: theme.transitions.duration.short + "ms"
},
- scroll: {
- "& .DraftEditor": {
- "&-editorContainer": {
- "& .public-DraftEditor-content": {
- lineHeight: 1.62
- }
- }
- }
- },
- smallIcon: {
- marginLeft: 10
+ rootActive: {
+ boxShadow: `inset 0px 0px 0 2px ${theme.palette.primary.main}`
}
};
},
{ name: "RichTextEditor" }
);
-function handleSave(
- value: any,
- initial: any,
- name: string,
- onChange: (event: ChangeEvent) => void
-) {
- if (value && !isEqual(value, initial)) {
- onChange({
- target: {
- name,
- value
+class NewEditor extends EditorJS {}
+
+const RichTextEditor: React.FC = ({
+ error,
+ helperText,
+ initial,
+ label,
+ name,
+ onChange
+}) => {
+ const classes = useStyles({});
+
+ const [isFocused, setFocus] = React.useState(false);
+ const editor = React.useRef();
+ const editorContainer = React.useRef();
+ React.useEffect(() => {
+ editor.current = new NewEditor({
+ data: initial,
+ holder: editorContainer.current,
+ tools: {
+ header: {
+ class: Header,
+ config: {
+ defaultLevel: 1,
+ levels: [1, 2, 3]
+ }
+ },
+ list: List,
+ quote: Quote,
+ strikethrough: createGenericInlineTool({
+ sanitize: {
+ s: true
+ },
+ shortcut: "CMD+S",
+ tagName: "s",
+ toolboxIcon: strikethroughIcon
+ })
}
});
- }
-}
-
-const RichTextEditor: React.FC = props => {
- const { error, helperText, initial, label, name, scroll, onChange } = props;
-
- const classes = useStyles(props);
+ }, []);
+ React.useEffect(() => () => editor.current.destroy(), []);
return (
-
-
-
+
+
setFocus(true)}
+ onBlur={() => setFocus(false)}
+ >
+
{label}
-
(
-
-
-
-
-
- )}
- >
- 0 ? initial : null
- }
- onSave={value => handleSave(value, initial, name, onChange)}
- blockTypes={[
- {
- icon: ,
- type: BLOCK_TYPE.HEADER_ONE
- },
- { icon: , type: BLOCK_TYPE.HEADER_TWO },
- { icon: , type: BLOCK_TYPE.HEADER_THREE },
- { icon: , type: BLOCK_TYPE.BLOCKQUOTE },
- {
- icon: ,
- type: BLOCK_TYPE.UNORDERED_LIST_ITEM
- },
- { icon: , type: BLOCK_TYPE.ORDERED_LIST_ITEM }
- ]}
- inlineStyles={[
- {
- icon: ,
- type: INLINE_STYLE.BOLD
- },
- {
- icon: ,
- type: INLINE_STYLE.ITALIC
- },
- {
- icon: ,
- type: INLINE_STYLE.STRIKETHROUGH
- }
- ]}
- enableLineBreak
- entityTypes={[
- {
- attributes: ["url"],
- decorator: LinkEntity,
- icon: ,
- source: LinkSource,
- type: ENTITY_TYPE.LINK
- }
- ]}
- />
-
{helperText && (
= props => {
};
RichTextEditor.displayName = "RichTextEditor";
-RichTextEditor.defaultProps = {
- scroll: true
-};
export default RichTextEditor;
diff --git a/src/icons/StrikethroughIcon.tsx b/src/icons/StrikethroughIcon.tsx
index b409b4c00..68957fd06 100644
--- a/src/icons/StrikethroughIcon.tsx
+++ b/src/icons/StrikethroughIcon.tsx
@@ -1,11 +1,3 @@
-import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
-import React from "react";
+const Strikethrough = ``;
-const HeaderOne = createSvgIcon(
- <>
-
- >,
- "HeaderOne"
-);
-
-export default HeaderOne;
+export default Strikethrough;
diff --git a/src/storybook/stories/components/RichTextEditor.tsx b/src/storybook/stories/components/RichTextEditor.tsx
index e645acb4f..9cfd5580c 100644
--- a/src/storybook/stories/components/RichTextEditor.tsx
+++ b/src/storybook/stories/components/RichTextEditor.tsx
@@ -1,108 +1,14 @@
+import { OutputData } from "@editorjs/editorjs";
import RichTextEditor from "@saleor/components/RichTextEditor";
import { storiesOf } from "@storybook/react";
-import { RawDraftContentState } from "draft-js";
import React from "react";
import CardDecorator from "../../CardDecorator";
import Decorator from "../../Decorator";
+import * as fixtures from "./fixtures.json";
+
+export const content: OutputData = fixtures.richTextEditor;
-export const content: RawDraftContentState = {
- blocks: [
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [{ length: 4, offset: 0, style: "BOLD" }],
- key: "rosn",
- text: "bold",
- type: "unstyled"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [{ length: 6, offset: 0, style: "ITALIC" }],
- key: "6tbch",
- text: "italic",
- type: "unstyled"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [{ length: 13, offset: 0, style: "STRIKETHROUGH" }],
- key: "1p044",
- text: "strikethrough",
- type: "unstyled"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [],
- key: "aven6",
- text: "h1",
- type: "header-one"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [],
- key: "9rabl",
- text: "h2",
- type: "header-two"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [],
- key: "bv0ac",
- text: "h3",
- type: "header-three"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [],
- key: "2ip7q",
- text: "blockquote",
- type: "blockquote"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [],
- key: "8r8ss",
- text: "ul",
- type: "unordered-list-item"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [],
- inlineStyleRanges: [],
- key: "911hc",
- text: "ol",
- type: "ordered-list-item"
- },
- {
- data: {},
- depth: 0,
- entityRanges: [{ key: 0, length: 4, offset: 0 }],
- inlineStyleRanges: [],
- key: "5aejo",
- text: "link",
- type: "unstyled"
- }
- ],
- entityMap: {
- "0": { data: { url: "#" }, mutability: "MUTABLE", type: "LINK" }
- }
-};
storiesOf("Generics / Rich text editor", module)
.addDecorator(CardDecorator)
.addDecorator(Decorator)
@@ -110,7 +16,7 @@ storiesOf("Generics / Rich text editor", module)
Sed iaculis urna et justo accumsan, eget porta est egestas. Nunc odio libero, pharetra in tristique eget, pellentesque in lectus. Sed sed laoreet orci. Suspendisse dui nibh, iaculis ac dui posuere, placerat elementum dolor. In sit amet aliquet nibh. Maecenas sed felis sed lectus gravida vulputate et a mi. Sed a tristique neque, ut euismod arcu. Donec quis aliquet massa. Curabitur arcu purus, facilisis quis posuere sit amet, pharetra at erat."
+ }
+ },
+ {
+ "type": "list",
+ "data": {
+ "style": "ordered",
+ "items": [
+ "Maecenas pretium aliquam odio, a iaculis diam dictum ut.",
+ "Vestibulum pulvinar, quam quis sollicitudin luctus, libero odio laoreet lectus, in tristique ligula dui et ex.
",
+ "Nam quis nibh sed elit fermentum interdum non eget quam."
+ ]
+ }
+ },
+ {
+ "type": "paragraph",
+ "data": {
+ "text": "Nulla sit amet cursus augue, in maximus tellus. Donec sit amet mollis neque, eget commodo odio."
+ }
+ },
+ { "type": "header", "data": { "text": "Morbi aliquam", "level": 2 } },
+ {
+ "type": "paragraph",
+ "data": {
+ "text": "Aliquam posuere nisi et ante malesuada egestas. Phasellus auctor risus a erat aliquam, tempus volutpat arcu rutrum. Duis bibendum id justo ut commodo. Suspendisse imperdiet tincidunt blandit."
+ }
+ },
+ { "type": "header", "data": { "text": "Nam ipsum purus", "level": 3 } },
+ {
+ "type": "paragraph",
+ "data": {
+ "text": "Nam ipsum purus, feugiat ut dapibus at, porttitor eget leo. Phasellus sodales urna quis mi viverra, non mollis magna tristique. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque viverra est sit amet nisi hendrerit, pharetra vehicula neque volutpat. Maecenas feugiat a nulla id blandit. Sed sagittis tempus libero non dignissim. In lectus felis, mattis vitae lacinia nec, volutpat eu elit. Proin ultricies lacus id felis placerat mollis. Integer ultricies eros nec mauris interdum, sit amet sodales ipsum elementum. Vivamus quis dapibus turpis, eu dignissim quam."
+ }
+ },
+ {
+ "type": "quote",
+ "data": {
+ "text": "Nam facilisis augue vel urna tristique rutrum id et tortor.",
+ "caption": "Morbi erat mi",
+ "alignment": "left"
+ }
+ },
+ {
+ "type": "header",
+ "data": { "text": "Tempor ac posuere nec", "level": 3 }
+ },
+ {
+ "type": "paragraph",
+ "data": {
+ "text": "Rhoncus ac lectus. Etiam viverra nisl feugiat tempus eleifend. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae."
+ }
+ },
+ {
+ "type": "list",
+ "data": {
+ "style": "unordered",
+ "items": [
+ "Phasellus nec ipsum non metus vestibulum semper",
+ "In tincidunt, dui vitae suscipit sodales, lacus justo porttitor nulla
"
+ ]
+ }
+ }
+ ],
+ "version": "2.19.0"
+ }
+}
From 815941fc821aaf8312063010dca3cec7d5f9a7d8 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Wed, 28 Oct 2020 16:30:44 +0100
Subject: [PATCH 02/23] Fix types
---
src/components/RichTextEditor/RichTextEditor.tsx | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/src/components/RichTextEditor/RichTextEditor.tsx b/src/components/RichTextEditor/RichTextEditor.tsx
index 16b9a576c..07093b6bd 100644
--- a/src/components/RichTextEditor/RichTextEditor.tsx
+++ b/src/components/RichTextEditor/RichTextEditor.tsx
@@ -5,21 +5,18 @@ import Quote from "@editorjs/quote";
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import Typography from "@material-ui/core/Typography";
-import { CreateCSSProperties } from "@material-ui/styles/withStyles";
import { FormChange } from "@saleor/hooks/useForm";
import strikethroughIcon from "@saleor/icons/StrikethroughIcon";
import classNames from "classnames";
-import createGenericInlineTool, {
- ItalicInlineTool,
- UnderlineInlineTool
-} from "editorjs-inline-tool";
+import createGenericInlineTool from "editorjs-inline-tool";
import React from "react";
export interface RichTextEditorProps {
disabled: boolean;
error: boolean;
helperText: string;
- initial: OutputData;
+ // TODO: Remove any type
+ initial: OutputData | any;
label: string;
name: string;
onChange: FormChange;
@@ -103,9 +100,7 @@ const RichTextEditor: React.FC = ({
error,
helperText,
initial,
- label,
- name,
- onChange
+ label
}) => {
const classes = useStyles({});
From 393b4a586027782853a463e75ddd459b49c6c3dd Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Thu, 29 Oct 2020 13:14:05 +0100
Subject: [PATCH 03/23] Fix styles
---
src/components/RichTextEditor/RichTextEditor.tsx | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/components/RichTextEditor/RichTextEditor.tsx b/src/components/RichTextEditor/RichTextEditor.tsx
index 07093b6bd..0bad6fae4 100644
--- a/src/components/RichTextEditor/RichTextEditor.tsx
+++ b/src/components/RichTextEditor/RichTextEditor.tsx
@@ -40,7 +40,7 @@ const useStyles = makeStyles(
label: {
color: theme.palette.text.secondary,
position: "absolute",
- top: theme.spacing(4),
+ top: theme.spacing(1),
transition: theme.transitions.duration.short + "ms"
},
labelActive: {
@@ -50,6 +50,10 @@ const useStyles = makeStyles(
"& .cdx-quote__text": {
minHeight: 24
},
+ "& .ce-block__content": {
+ margin: 0,
+ maxWidth: "unset"
+ },
"& .ce-conversion-tool": {
...hover
},
@@ -80,10 +84,11 @@ const useStyles = makeStyles(
"&:hover": {
borderColor: theme.palette.primary.main
},
- border: `1px solid ${theme.palette.divider}`,
- borderRadius: 8,
+ border: `1px solid ${fade(theme.palette.text.secondary, 0.4)}`,
+ borderRadius: 4,
boxShadow: `inset 0 0 0 0 ${theme.palette.primary.main}`,
padding: theme.spacing(3, 2),
+ position: "relative",
transition: theme.transitions.duration.short + "ms"
},
rootActive: {
From 88bd52763cf69d8fd38e2a27ea638c2187f90791 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Tue, 3 Nov 2020 12:35:36 +0100
Subject: [PATCH 04/23] Add ability to save data
---
.../RichTextEditor/RichTextEditor.tsx | 74 +++++++++++--------
.../ProductDetailsForm/ProductDetailsForm.tsx | 20 ++---
.../ProductUpdatePage/ProductUpdatePage.tsx | 19 ++---
.../components/ProductUpdatePage/form.tsx | 56 ++++++++++----
src/products/utils/data.ts | 3 +-
5 files changed, 100 insertions(+), 72 deletions(-)
diff --git a/src/components/RichTextEditor/RichTextEditor.tsx b/src/components/RichTextEditor/RichTextEditor.tsx
index 0bad6fae4..8653a9616 100644
--- a/src/components/RichTextEditor/RichTextEditor.tsx
+++ b/src/components/RichTextEditor/RichTextEditor.tsx
@@ -5,21 +5,20 @@ import Quote from "@editorjs/quote";
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import Typography from "@material-ui/core/Typography";
-import { FormChange } from "@saleor/hooks/useForm";
import strikethroughIcon from "@saleor/icons/StrikethroughIcon";
import classNames from "classnames";
import createGenericInlineTool from "editorjs-inline-tool";
import React from "react";
+export type RichTextEditorChange = (data: OutputData) => void;
export interface RichTextEditorProps {
+ data: OutputData;
disabled: boolean;
error: boolean;
helperText: string;
- // TODO: Remove any type
- initial: OutputData | any;
label: string;
name: string;
- onChange: FormChange;
+ onChange: RichTextEditorChange;
}
const useStyles = makeStyles(
@@ -99,45 +98,56 @@ const useStyles = makeStyles(
{ name: "RichTextEditor" }
);
-class NewEditor extends EditorJS {}
-
const RichTextEditor: React.FC = ({
+ data,
error,
helperText,
- initial,
- label
+ label,
+ onChange
}) => {
const classes = useStyles({});
const [isFocused, setFocus] = React.useState(false);
const editor = React.useRef();
const editorContainer = React.useRef();
- React.useEffect(() => {
- editor.current = new NewEditor({
- data: initial,
- holder: editorContainer.current,
- tools: {
- header: {
- class: Header,
- config: {
- defaultLevel: 1,
- levels: [1, 2, 3]
- }
- },
- list: List,
- quote: Quote,
- strikethrough: createGenericInlineTool({
- sanitize: {
- s: true
+ React.useEffect(
+ () => {
+ if (data) {
+ editor.current = new EditorJS({
+ data,
+ holder: editorContainer.current,
+ onChange: async api => {
+ const savedData = await api.saver.save();
+ onChange(savedData);
},
- shortcut: "CMD+S",
- tagName: "s",
- toolboxIcon: strikethroughIcon
- })
+ tools: {
+ header: {
+ class: Header,
+ config: {
+ defaultLevel: 1,
+ levels: [1, 2, 3]
+ }
+ },
+ list: List,
+ quote: Quote,
+ strikethrough: createGenericInlineTool({
+ sanitize: {
+ s: true
+ },
+ shortcut: "CMD+S",
+ tagName: "s",
+ toolboxIcon: strikethroughIcon
+ })
+ }
+ });
}
- });
- }, []);
- React.useEffect(() => () => editor.current.destroy(), []);
+
+ return editor.current?.destroy;
+ },
+ // Rerender editor only if changed from undefined to defined state
+ [data === undefined]
+ );
+ React.useEffect(() => editor.current?.destroy, []);
return (
diff --git a/src/products/components/ProductDetailsForm/ProductDetailsForm.tsx b/src/products/components/ProductDetailsForm/ProductDetailsForm.tsx
index aac585345..a533cf97c 100644
--- a/src/products/components/ProductDetailsForm/ProductDetailsForm.tsx
+++ b/src/products/components/ProductDetailsForm/ProductDetailsForm.tsx
@@ -1,27 +1,27 @@
+import { OutputData } from "@editorjs/editorjs";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField";
import CardTitle from "@saleor/components/CardTitle";
import FormSpacer from "@saleor/components/FormSpacer";
-import RichTextEditor from "@saleor/components/RichTextEditor";
+import RichTextEditor, {
+ RichTextEditorChange
+} from "@saleor/components/RichTextEditor";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { commonMessages } from "@saleor/intl";
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
-import { RawDraftContentState } from "draft-js";
import React from "react";
import { useIntl } from "react-intl";
interface ProductDetailsFormProps {
data: {
- description: RawDraftContentState;
+ description: OutputData;
name: string;
};
disabled?: boolean;
errors: ProductErrorFragment[];
- // Draftail isn't controlled - it needs only initial input
- // because it's autosaving on its own.
- // Ref https://github.com/mirumee/saleor/issues/4470
- initialDescription: RawDraftContentState;
+
+ onDescriptionChange: RichTextEditorChange;
onChange(event: any);
}
@@ -29,7 +29,7 @@ export const ProductDetailsForm: React.FC
= ({
data,
disabled,
errors,
- initialDescription,
+ onDescriptionChange,
onChange
}) => {
const intl = useIntl();
@@ -57,13 +57,13 @@ export const ProductDetailsForm: React.FC = ({
/>
diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx
index 3e99347e2..4d51ad9cd 100644
--- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx
+++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx
@@ -1,3 +1,4 @@
+import { OutputData } from "@editorjs/editorjs";
import AppHeader from "@saleor/components/AppHeader";
import AvailabilityCard from "@saleor/components/AvailabilityCard";
import CardSpacer from "@saleor/components/CardSpacer";
@@ -19,7 +20,6 @@ import { maybe } from "@saleor/misc";
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
import { FetchMoreProps, ListActions, ReorderAction } from "@saleor/types";
-import { convertFromRaw, RawDraftContentState } from "draft-js";
import React from "react";
import { useIntl } from "react-intl";
@@ -75,11 +75,12 @@ export interface ProductUpdatePageProps extends ListActions {
}
export interface ProductUpdatePageSubmitData extends ProductUpdatePageFormData {
+ addStocks: ProductStockInput[];
attributes: ProductAttributeInput[];
collections: string[];
- addStocks: ProductStockInput[];
- updateStocks: ProductStockInput[];
+ description: OutputData;
removeStocks: string[];
+ updateStocks: ProductStockInput[];
}
export const ProductUpdatePage: React.FC = ({
@@ -135,10 +136,6 @@ export const ProductUpdatePage: React.FC = ({
product?.taxType.description
);
- const initialDescription = maybe(() =>
- JSON.parse(product.descriptionJson)
- );
-
const categories = getChoices(categoryChoiceList);
const collections = getChoices(collectionChoiceList);
const currency = product?.variants[0]?.price.currency;
@@ -175,7 +172,7 @@ export const ProductUpdatePage: React.FC = ({
data={data}
disabled={disabled}
errors={errors}
- initialDescription={initialDescription}
+ onDescriptionChange={handlers.changeDescription}
onChange={change}
/>
@@ -262,11 +259,7 @@ export const ProductUpdatePage: React.FC = ({
title={data.seoTitle}
titlePlaceholder={data.name}
description={data.seoDescription}
- descriptionPlaceholder={maybe(() =>
- convertFromRaw(data.description)
- .getPlainText()
- .slice(0, 300)
- )}
+ descriptionPlaceholder={""} // TODO: cast description to string
slug={data.slug}
slugPlaceholder={data.name}
loading={disabled}
diff --git a/src/products/components/ProductUpdatePage/form.tsx b/src/products/components/ProductUpdatePage/form.tsx
index 009b8b27a..ff4c40393 100644
--- a/src/products/components/ProductUpdatePage/form.tsx
+++ b/src/products/components/ProductUpdatePage/form.tsx
@@ -1,5 +1,7 @@
+import { OutputData } from "@editorjs/editorjs";
import { MetadataFormData } from "@saleor/components/Metadata";
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
+import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
import useFormset, {
@@ -21,7 +23,6 @@ import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
-import { RawDraftContentState } from "draft-js";
import { diff } from "fast-array-diff";
import React from "react";
@@ -35,7 +36,6 @@ export interface ProductUpdateFormData extends MetadataFormData {
changeTaxCode: boolean;
chargeTaxes: boolean;
collections: string[];
- description: RawDraftContentState;
isAvailable: boolean;
isAvailableForPurchase: boolean;
isPublished: boolean;
@@ -52,27 +52,36 @@ export interface ProductUpdateFormData extends MetadataFormData {
}
export interface ProductUpdateData extends ProductUpdateFormData {
attributes: ProductAttributeInput[];
+ description: OutputData;
stocks: ProductStockInput[];
}
export interface ProductUpdateSubmitData extends ProductUpdateFormData {
attributes: ProductAttributeInput[];
collections: string[];
+ description: OutputData;
addStocks: ProductStockInput[];
updateStocks: ProductStockInput[];
removeStocks: string[];
}
-type ProductUpdateHandlers = Record<
- "changeMetadata" | "selectCategory" | "selectCollection" | "selectTaxRate",
- FormChange
-> &
- Record<
- "changeStock" | "selectAttribute" | "selectAttributeMultiple",
- FormsetChange
- > &
- Record<"addStock" | "deleteStock", (id: string) => void>;
+interface ProductUpdateHandlers
+ extends Record<
+ | "changeMetadata"
+ | "selectCategory"
+ | "selectCollection"
+ | "selectTaxRate",
+ FormChange
+ >,
+ Record<
+ "changeStock" | "selectAttribute" | "selectAttributeMultiple",
+ FormsetChange
+ >,
+ Record<"addStock" | "deleteStock", (id: string) => void> {
+ changeDescription: RichTextEditorChange;
+}
export interface UseProductUpdateFormResult {
change: FormChange;
+
data: ProductUpdateData;
handlers: ProductUpdateHandlers;
hasChanged: boolean;
@@ -155,6 +164,15 @@ function useProductUpdateForm(
);
const attributes = useFormset(getAttributeInputFromProduct(product));
const stocks = useFormset(getStockInputFromProduct(product));
+ const description = React.useRef();
+
+ React.useEffect(() => {
+ try {
+ description.current = JSON.parse(product.descriptionJson);
+ } catch {
+ description.current = undefined;
+ }
+ }, [product]);
const {
isMetadataModified,
@@ -209,28 +227,36 @@ function useProductUpdateForm(
opts.taxTypes
);
const changeMetadata = makeMetadataChangeHandler(handleChange);
+ const changeDescription: RichTextEditorChange = data => {
+ triggerChange();
+ description.current = data;
+ };
const data: ProductUpdateData = {
...form.data,
attributes: attributes.data,
+ description: description.current,
stocks: stocks.data
};
- const submitData: ProductUpdateSubmitData = {
+ // Need to make it function to always have description.current up to date
+ const getSubmitData = (): ProductUpdateSubmitData => ({
...data,
...getAvailabilityData(data),
...getStocksData(product, stocks.data),
...getMetadata(data, isMetadataModified, isPrivateMetadataModified),
addStocks: [],
- attributes: attributes.data
- };
+ attributes: attributes.data,
+ description: description.current
+ });
- const submit = () => handleFormSubmit(submitData, onSubmit, setChanged);
+ const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
return {
change: handleChange,
data,
handlers: {
addStock: handleStockAdd,
+ changeDescription,
changeMetadata,
changeStock: handleStockChange,
deleteStock: handleStockDelete,
diff --git a/src/products/utils/data.ts b/src/products/utils/data.ts
index f581aecac..f48043e1e 100644
--- a/src/products/utils/data.ts
+++ b/src/products/utils/data.ts
@@ -1,3 +1,4 @@
+import { OutputData } from "@editorjs/editorjs";
import { MetadataFormData } from "@saleor/components/Metadata/types";
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
@@ -175,7 +176,6 @@ export interface ProductUpdatePageFormData extends MetadataFormData {
changeTaxCode: boolean;
chargeTaxes: boolean;
collections: string[];
- description: RawDraftContentState;
isAvailable: boolean;
isAvailableForPurchase: boolean;
isPublished: boolean;
@@ -205,7 +205,6 @@ export function getProductUpdatePageFormData(
() => product.collections.map(collection => collection.id),
[]
),
- description: maybe(() => JSON.parse(product.descriptionJson)),
isAvailable: !!product?.isAvailable,
isAvailableForPurchase: !!product?.isAvailableForPurchase,
isPublished: maybe(() => product.isPublished, false),
From 437df6fe9a11dacb7f8b098672b385244864ce45 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Tue, 3 Nov 2020 14:53:17 +0100
Subject: [PATCH 05/23] wip
---
.../RichTextEditor/RichTextEditor.tsx | 8 ++-
.../ProductCreatePage/ProductCreatePage.tsx | 9 +--
.../components/ProductCreatePage/form.tsx | 53 ++++++++------
.../components/ProductUpdatePage/form.tsx | 18 ++---
src/utils/richText/useRichText.test.ts | 72 +++++++++++++++++++
src/utils/richText/useRichText.ts | 28 ++++++++
6 files changed, 146 insertions(+), 42 deletions(-)
create mode 100644 src/utils/richText/useRichText.test.ts
create mode 100644 src/utils/richText/useRichText.ts
diff --git a/src/components/RichTextEditor/RichTextEditor.tsx b/src/components/RichTextEditor/RichTextEditor.tsx
index 8653a9616..2eb58bd04 100644
--- a/src/components/RichTextEditor/RichTextEditor.tsx
+++ b/src/components/RichTextEditor/RichTextEditor.tsx
@@ -19,6 +19,7 @@ export interface RichTextEditorProps {
label: string;
name: string;
onChange: RichTextEditorChange;
+ onReady?: () => void;
}
const useStyles = makeStyles(
@@ -86,7 +87,10 @@ const useStyles = makeStyles(
border: `1px solid ${fade(theme.palette.text.secondary, 0.4)}`,
borderRadius: 4,
boxShadow: `inset 0 0 0 0 ${theme.palette.primary.main}`,
+ fontSize: theme.typography.body1.fontSize,
padding: theme.spacing(3, 2),
+ paddingBottom: theme.spacing(),
+ paddingLeft: 10,
position: "relative",
transition: theme.transitions.duration.short + "ms"
},
@@ -103,7 +107,8 @@ const RichTextEditor: React.FC = ({
error,
helperText,
label,
- onChange
+ onChange,
+ onReady
}) => {
const classes = useStyles({});
@@ -120,6 +125,7 @@ const RichTextEditor: React.FC = ({
const savedData = await api.saver.save();
onChange(savedData);
},
+ onReady,
tools: {
header: {
class: Header,
diff --git a/src/products/components/ProductCreatePage/ProductCreatePage.tsx b/src/products/components/ProductCreatePage/ProductCreatePage.tsx
index ffebe8145..b288068b4 100644
--- a/src/products/components/ProductCreatePage/ProductCreatePage.tsx
+++ b/src/products/components/ProductCreatePage/ProductCreatePage.tsx
@@ -86,12 +86,6 @@ export const ProductCreatePage: React.FC = ({
const intl = useIntl();
const localizeDate = useDateLocalize();
- // Ensures that it will not change after component rerenders, because it
- // generates different block keys and it causes editor to lose its content.
- const initialDescription = React.useRef(
- convertToRaw(ContentState.createFromText(""))
- );
-
// Display values
const [selectedCategory, setSelectedCategory] = useStateFromProps(
initial?.category || ""
@@ -144,8 +138,8 @@ export const ProductCreatePage: React.FC = ({
data={data}
disabled={disabled}
errors={errors}
- initialDescription={initialDescription.current}
onChange={change}
+ onDescriptionChange={handlers.changeDescription}
/>
{data.attributes.length > 0 && (
@@ -167,6 +161,7 @@ export const ProductCreatePage: React.FC = ({
weightUnit={weightUnit}
onChange={change}
/>
+
&
- Record<
- "changeStock" | "selectAttribute" | "selectAttributeMultiple",
- FormsetChange
- > &
- Record<"addStock" | "deleteStock", (id: string) => void>;
+interface ProductCreateHandlers
+ extends Record<
+ | "changeMetadata"
+ | "selectCategory"
+ | "selectCollection"
+ | "selectProductType"
+ | "selectTaxRate",
+ FormChange
+ >,
+ Record<
+ "changeStock" | "selectAttribute" | "selectAttributeMultiple",
+ FormsetChange
+ >,
+ Record<"addStock" | "deleteStock", (id: string) => void> {
+ changeDescription: RichTextEditorChange;
+}
export interface UseProductCreateFormResult {
change: FormChange;
data: ProductCreateData;
@@ -106,7 +111,7 @@ const defaultInitialFormData: ProductCreateFormData &
changeTaxCode: false,
chargeTaxes: false,
collections: [],
- description: {} as any,
+ description: null,
isAvailable: false,
isAvailableForPurchase: false,
isPublished: false,
@@ -117,7 +122,7 @@ const defaultInitialFormData: ProductCreateFormData &
publicationDate: "",
seoDescription: "",
seoTitle: "",
- sku: null,
+ sku: "",
slug: "",
stockQuantity: null,
taxCode: null,
@@ -152,6 +157,10 @@ function useProductCreateForm(
const [productType, setProductType] = useStateFromProps(
initialProductType || null
);
+ const [description, changeDescription] = useRichText({
+ initial: null,
+ triggerChange
+ });
const {
makeChangeHandler: makeMetadataChangeHandler
@@ -211,19 +220,21 @@ function useProductCreateForm(
);
const changeMetadata = makeMetadataChangeHandler(handleChange);
- const data: ProductCreateData = {
+ const getData = (): ProductCreateData => ({
...form.data,
attributes: attributes.data,
+ description: description.current,
productType,
stocks: stocks.data
- };
- const submit = () => onSubmit(data);
+ });
+ const submit = () => onSubmit(getData());
return {
change: handleChange,
- data,
+ data: getData(),
handlers: {
addStock: handleStockAdd,
+ changeDescription,
changeMetadata,
changeStock: handleStockChange,
deleteStock: handleStockDelete,
diff --git a/src/products/components/ProductUpdatePage/form.tsx b/src/products/components/ProductUpdatePage/form.tsx
index ff4c40393..c1f2e8fa2 100644
--- a/src/products/components/ProductUpdatePage/form.tsx
+++ b/src/products/components/ProductUpdatePage/form.tsx
@@ -23,6 +23,7 @@ import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
+import useRichText from "@saleor/utils/richText/useRichText";
import { diff } from "fast-array-diff";
import React from "react";
@@ -164,15 +165,10 @@ function useProductUpdateForm(
);
const attributes = useFormset(getAttributeInputFromProduct(product));
const stocks = useFormset(getStockInputFromProduct(product));
- const description = React.useRef();
-
- React.useEffect(() => {
- try {
- description.current = JSON.parse(product.descriptionJson);
- } catch {
- description.current = undefined;
- }
- }, [product]);
+ const [description, changeDescription] = useRichText({
+ initial: product?.descriptionJson,
+ triggerChange
+ });
const {
isMetadataModified,
@@ -227,10 +223,6 @@ function useProductUpdateForm(
opts.taxTypes
);
const changeMetadata = makeMetadataChangeHandler(handleChange);
- const changeDescription: RichTextEditorChange = data => {
- triggerChange();
- description.current = data;
- };
const data: ProductUpdateData = {
...form.data,
diff --git a/src/utils/richText/useRichText.test.ts b/src/utils/richText/useRichText.test.ts
new file mode 100644
index 000000000..26fc9c782
--- /dev/null
+++ b/src/utils/richText/useRichText.test.ts
@@ -0,0 +1,72 @@
+import { OutputData } from "@editorjs/editorjs";
+import { renderHook } from "@testing-library/react-hooks";
+
+import useRichText from "./useRichText";
+
+type Fixtures = Record<"short" | "long", OutputData>;
+const fixtures: Fixtures = {
+ long: {
+ blocks: [
+ {
+ data: {
+ level: 1,
+ text: "Some header"
+ },
+ type: "header"
+ },
+ {
+ data: {
+ text: "Some text"
+ },
+ type: "paragraph"
+ }
+ ]
+ },
+ short: {
+ blocks: [
+ {
+ data: {
+ text: "Some text"
+ },
+ type: "paragraph"
+ }
+ ]
+ }
+};
+
+describe("useRichText", () => {
+ it("properly saves data in form", () => {
+ const triggerChange = jest.fn();
+ const hook = renderHook(() =>
+ useRichText({
+ triggerChange
+ })
+ );
+
+ const [data, change] = hook.result.current;
+ expect(data.current).toBe(undefined);
+
+ change(fixtures.short);
+
+ expect(data.current).toMatchObject(fixtures.short);
+ expect(triggerChange).toHaveBeenCalled();
+ });
+
+ it("properly updates data in form", () => {
+ const triggerChange = jest.fn();
+ const hook = renderHook(() =>
+ useRichText({
+ initial: JSON.stringify(fixtures.short),
+ triggerChange
+ })
+ );
+
+ const [data, change] = hook.result.current;
+ expect(data.current).toMatchObject(fixtures.short);
+
+ change(fixtures.long);
+
+ expect(data.current).toMatchObject(fixtures.long);
+ expect(triggerChange).toHaveBeenCalled();
+ });
+});
diff --git a/src/utils/richText/useRichText.ts b/src/utils/richText/useRichText.ts
new file mode 100644
index 000000000..58613193b
--- /dev/null
+++ b/src/utils/richText/useRichText.ts
@@ -0,0 +1,28 @@
+import { OutputData } from "@editorjs/editorjs";
+import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
+import { MutableRefObject, useEffect, useRef } from "react";
+
+function useRichText(opts: {
+ initial?: string | null;
+ triggerChange: () => void;
+}): [MutableRefObject, RichTextEditorChange] {
+ const data = useRef(
+ opts.initial === null ? { blocks: [] } : undefined
+ );
+ useEffect(() => {
+ try {
+ data.current = JSON.parse(opts.initial);
+ } catch {
+ data.current = undefined;
+ }
+ }, [opts.initial]);
+
+ const change: RichTextEditorChange = newData => {
+ opts.triggerChange();
+ data.current = newData;
+ };
+
+ return [data, change];
+}
+
+export default useRichText;
From 28cc74c9548a7f2ab8b6c9a4cdd5f7eda631ec17 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Tue, 3 Nov 2020 17:17:37 +0100
Subject: [PATCH 06/23] Add rich text editor to product pages
---
src/components/RichTextEditor/RichTextEditor.tsx | 13 +++++++++++++
.../components/ProductUpdatePage/form.tsx | 10 +---------
.../components/ProductVariantPage/form.tsx | 9 ++-------
src/utils/metadata/getMetadata.ts | 16 ++++++++++++++++
src/utils/richText/useRichText.ts | 2 +-
5 files changed, 33 insertions(+), 17 deletions(-)
create mode 100644 src/utils/metadata/getMetadata.ts
diff --git a/src/components/RichTextEditor/RichTextEditor.tsx b/src/components/RichTextEditor/RichTextEditor.tsx
index 2eb58bd04..0a5a6de76 100644
--- a/src/components/RichTextEditor/RichTextEditor.tsx
+++ b/src/components/RichTextEditor/RichTextEditor.tsx
@@ -74,6 +74,19 @@ const useStyles = makeStyles(
"& .ce-inline-toolbar__toggler-and-button-wrapper": {
paddingRight: 0
},
+ "& .ce-toolbar__actions": {
+ right: 0,
+ top: 0
+ },
+ "& .ce-toolbar__content": {
+ maxWidth: "unset"
+ },
+ "& .ce-toolbar__plus": {
+ left: -9
+ },
+ "& .ce-toolbox.ce-toolbox--opened": {
+ left: 16
+ },
"& .codex-editor__redactor": {
marginRight: `${theme.spacing(4)}px !important`,
paddingBottom: "0 !important"
diff --git a/src/products/components/ProductUpdatePage/form.tsx b/src/products/components/ProductUpdatePage/form.tsx
index c1f2e8fa2..21e59cd0e 100644
--- a/src/products/components/ProductUpdatePage/form.tsx
+++ b/src/products/components/ProductUpdatePage/form.tsx
@@ -22,6 +22,7 @@ import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/Searc
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
+import getMetadata from "@saleor/utils/metadata/getMetadata";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import useRichText from "@saleor/utils/richText/useRichText";
import { diff } from "fast-array-diff";
@@ -143,15 +144,6 @@ const getStocksData = (
};
};
-const getMetadata = (
- data: ProductUpdateFormData,
- isMetadataModified: boolean,
- isPrivateMetadataModified: boolean
-) => ({
- metadata: isMetadataModified ? data.metadata : undefined,
- privateMetadata: isPrivateMetadataModified ? data.privateMetadata : undefined
-});
-
function useProductUpdateForm(
product: ProductDetails_product,
onSubmit: (data: ProductUpdateSubmitData) => SubmitPromise,
diff --git a/src/products/components/ProductVariantPage/form.tsx b/src/products/components/ProductVariantPage/form.tsx
index 9d9fc8203..b667c9cb4 100644
--- a/src/products/components/ProductVariantPage/form.tsx
+++ b/src/products/components/ProductVariantPage/form.tsx
@@ -11,6 +11,7 @@ import {
} from "@saleor/products/utils/data";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
+import getMetadata from "@saleor/utils/metadata/getMetadata";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { diff } from "fast-array-diff";
import React from "react";
@@ -117,11 +118,6 @@ function useProductVariantUpdateForm(
stocks.remove(id);
};
- const metadata = isMetadataModified ? form.data.metadata : undefined;
- const privateMetadata = isPrivateMetadataModified
- ? form.data.privateMetadata
- : undefined;
-
const dataStocks = stocks.data.map(stock => stock.id);
const variantStocks = variant?.stocks.map(stock => stock.warehouse.id) || [];
const stockDiff = diff(variantStocks, dataStocks);
@@ -140,10 +136,9 @@ function useProductVariantUpdateForm(
};
const submitData: ProductVariantUpdateSubmitData = {
...form.data,
+ ...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
addStocks,
attributes: attributes.data,
- metadata,
- privateMetadata,
removeStocks: stockDiff.removed,
updateStocks
};
diff --git a/src/utils/metadata/getMetadata.ts b/src/utils/metadata/getMetadata.ts
new file mode 100644
index 000000000..820f3a766
--- /dev/null
+++ b/src/utils/metadata/getMetadata.ts
@@ -0,0 +1,16 @@
+import { MetadataFormData } from "@saleor/components/Metadata";
+
+function getMetadata(
+ data: MetadataFormData,
+ isMetadataModified: boolean,
+ isPrivateMetadataModified: boolean
+) {
+ return {
+ metadata: isMetadataModified ? data.metadata : undefined,
+ privateMetadata: isPrivateMetadataModified
+ ? data.privateMetadata
+ : undefined
+ };
+}
+
+export default getMetadata;
diff --git a/src/utils/richText/useRichText.ts b/src/utils/richText/useRichText.ts
index 58613193b..fa51fb74a 100644
--- a/src/utils/richText/useRichText.ts
+++ b/src/utils/richText/useRichText.ts
@@ -3,7 +3,7 @@ import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
import { MutableRefObject, useEffect, useRef } from "react";
function useRichText(opts: {
- initial?: string | null;
+ initial: string | null;
triggerChange: () => void;
}): [MutableRefObject, RichTextEditorChange] {
const data = useRef(
From 9adde24a4813f25f3c429870c626de5cb2f66fbb Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Tue, 3 Nov 2020 17:17:47 +0100
Subject: [PATCH 07/23] Add rich text editor to category pages
---
.../CategoryCreatePage/CategoryCreatePage.tsx | 128 +++-----
.../components/CategoryCreatePage/form.tsx | 100 ++++++
.../CategoryDetailsForm.tsx | 19 +-
.../CategoryUpdatePage/CategoryUpdatePage.tsx | 305 +++++++-----------
.../components/CategoryUpdatePage/form.tsx | 107 ++++++
5 files changed, 391 insertions(+), 268 deletions(-)
create mode 100644 src/categories/components/CategoryCreatePage/form.tsx
create mode 100644 src/categories/components/CategoryUpdatePage/form.tsx
diff --git a/src/categories/components/CategoryCreatePage/CategoryCreatePage.tsx b/src/categories/components/CategoryCreatePage/CategoryCreatePage.tsx
index 0ac329841..91b91d136 100644
--- a/src/categories/components/CategoryCreatePage/CategoryCreatePage.tsx
+++ b/src/categories/components/CategoryCreatePage/CategoryCreatePage.tsx
@@ -2,43 +2,23 @@ import AppHeader from "@saleor/components/AppHeader";
import { CardSpacer } from "@saleor/components/CardSpacer";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
-import Form from "@saleor/components/Form";
-import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
+import Metadata from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { sectionNames } from "@saleor/intl";
-import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
-import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
import React from "react";
import { useIntl } from "react-intl";
import CategoryDetailsForm from "../../components/CategoryDetailsForm";
-
-export interface FormData extends MetadataFormData {
- description: RawDraftContentState;
- name: string;
- slug: string;
- seoTitle: string;
- seoDescription: string;
-}
-
-const initialData: FormData = {
- description: convertToRaw(ContentState.createFromText("")),
- metadata: [],
- name: "",
- privateMetadata: [],
- seoDescription: "",
- seoTitle: "",
- slug: ""
-};
+import CategoryCreateForm, { CategoryCreateData } from "./form";
export interface CategoryCreatePageProps {
errors: ProductErrorFragment[];
disabled: boolean;
saveButtonBarState: ConfirmButtonTransitionState;
- onSubmit(data: FormData);
+ onSubmit(data: CategoryCreateData);
onBack();
}
@@ -50,63 +30,57 @@ export const CategoryCreatePage: React.FC = ({
saveButtonBarState
}) => {
const intl = useIntl();
- const {
- makeChangeHandler: makeMetadataChangeHandler
- } = useMetadataChangeTrigger();
return (
-
JSON.parse(category.descriptionJson))}
name="description"
- onChange={onChange}
+ onChange={onDescriptionChange}
/>
diff --git a/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx b/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx
index 96c46e1c2..9446f74c3 100644
--- a/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx
+++ b/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx
@@ -5,9 +5,7 @@ import { CardSpacer } from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
-import Form from "@saleor/components/Form";
import Metadata from "@saleor/components/Metadata/Metadata";
-import { MetadataFormData } from "@saleor/components/Metadata/types";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm";
@@ -15,9 +13,6 @@ import { Tab, TabContainer } from "@saleor/components/Tab";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { SubmitPromise } from "@saleor/hooks/useForm";
import { sectionNames } from "@saleor/intl";
-import { mapMetadataItemToInput } from "@saleor/utils/maps";
-import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
-import { RawDraftContentState } from "draft-js";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
@@ -32,15 +27,7 @@ import {
} from "../../types/CategoryDetails";
import CategoryBackground from "../CategoryBackground";
import CategoryProducts from "../CategoryProducts";
-
-export interface FormData extends MetadataFormData {
- backgroundImageAlt: string;
- description: RawDraftContentState;
- name: string;
- slug: string;
- seoTitle: string;
- seoDescription: string;
-}
+import CategoryUpdateForm, { CategoryUpdateData } from "./form";
export enum CategoryPageTab {
categories = "categories",
@@ -62,7 +49,7 @@ export interface CategoryUpdatePageProps
};
saveButtonBarState: ConfirmButtonTransitionState;
onImageDelete: () => void;
- onSubmit: (data: FormData) => SubmitPromise;
+ onSubmit: (data: CategoryUpdateData) => SubmitPromise;
onImageUpload(file: File);
onNextPage();
onPreviousPage();
@@ -106,180 +93,136 @@ export const CategoryUpdatePage: React.FC = ({
toggleAll
}: CategoryUpdatePageProps) => {
const intl = useIntl();
- const {
- isMetadataModified,
- isPrivateMetadataModified,
- makeChangeHandler: makeMetadataChangeHandler
- } = useMetadataChangeTrigger();
-
- const initialData: FormData = category
- ? {
- backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""),
- description: maybe(() => JSON.parse(category.descriptionJson)),
- metadata: category?.metadata?.map(mapMetadataItemToInput),
- name: category.name || "",
- privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
- seoDescription: category.seoDescription || "",
- seoTitle: category.seoTitle || "",
- slug: category?.slug || ""
- }
- : {
- backgroundImageAlt: "",
- description: "",
- metadata: undefined,
- name: "",
- privateMetadata: undefined,
- seoDescription: "",
- seoTitle: "",
- slug: ""
- };
-
- const handleSubmit = (data: FormData) => {
- const metadata = isMetadataModified ? data.metadata : undefined;
- const privateMetadata = isPrivateMetadataModified
- ? data.privateMetadata
- : undefined;
-
- return onSubmit({
- ...data,
- metadata,
- privateMetadata
- });
- };
return (
-
+ )}
+
+
+ )}
+
);
};
CategoryUpdatePage.displayName = "CategoryUpdatePage";
diff --git a/src/categories/components/CategoryUpdatePage/form.tsx b/src/categories/components/CategoryUpdatePage/form.tsx
new file mode 100644
index 000000000..a98c80c1f
--- /dev/null
+++ b/src/categories/components/CategoryUpdatePage/form.tsx
@@ -0,0 +1,107 @@
+import { OutputData } from "@editorjs/editorjs";
+import { CategoryDetails_category } from "@saleor/categories/types/CategoryDetails";
+import { MetadataFormData } from "@saleor/components/Metadata";
+import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
+import useForm, { FormChange } from "@saleor/hooks/useForm";
+import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
+import { mapMetadataItemToInput } from "@saleor/utils/maps";
+import getMetadata from "@saleor/utils/metadata/getMetadata";
+import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
+import useRichText from "@saleor/utils/richText/useRichText";
+import React from "react";
+
+export interface CategoryUpdateFormData extends MetadataFormData {
+ backgroundImageAlt: string;
+ name: string;
+ slug: string;
+ seoTitle: string;
+ seoDescription: string;
+}
+export interface CategoryUpdateData extends CategoryUpdateFormData {
+ description: OutputData;
+}
+
+interface CategoryUpdateHandlers {
+ changeMetadata: FormChange;
+ changeDescription: RichTextEditorChange;
+}
+export interface UseCategoryUpdateFormResult {
+ change: FormChange;
+ data: CategoryUpdateData;
+ handlers: CategoryUpdateHandlers;
+ hasChanged: boolean;
+ submit: () => Promise;
+}
+
+export interface CategoryUpdateFormProps {
+ children: (props: UseCategoryUpdateFormResult) => React.ReactNode;
+ category: CategoryDetails_category;
+ onSubmit: (data: CategoryUpdateData) => Promise;
+}
+
+function useCategoryUpdateForm(
+ category: CategoryDetails_category,
+ onSubmit: (data: CategoryUpdateData) => Promise
+): UseCategoryUpdateFormResult {
+ const [changed, setChanged] = React.useState(false);
+ const triggerChange = () => setChanged(true);
+
+ const form = useForm({
+ backgroundImageAlt: category?.backgroundImage?.alt || "",
+ metadata: category?.metadata?.map(mapMetadataItemToInput),
+ name: category?.name || "",
+ privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
+ seoDescription: category?.seoDescription || "",
+ seoTitle: category?.seoTitle || "",
+ slug: category?.slug || ""
+ });
+ const [description, changeDescription] = useRichText({
+ initial: category?.descriptionJson,
+ triggerChange
+ });
+
+ const {
+ isMetadataModified,
+ isPrivateMetadataModified,
+ makeChangeHandler: makeMetadataChangeHandler
+ } = useMetadataChangeTrigger();
+
+ const handleChange: FormChange = (event, cb) => {
+ form.change(event, cb);
+ triggerChange();
+ };
+ const changeMetadata = makeMetadataChangeHandler(handleChange);
+
+ // Need to make it function to always have description.current up to date
+ const getData = (): CategoryUpdateData => ({
+ ...form.data,
+ ...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
+ description: description.current
+ });
+
+ const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
+
+ return {
+ change: handleChange,
+ data: getData(),
+ handlers: {
+ changeDescription,
+ changeMetadata
+ },
+ hasChanged: changed,
+ submit
+ };
+}
+
+const CategoryUpdateForm: React.FC = ({
+ children,
+ category,
+ onSubmit
+}) => {
+ const props = useCategoryUpdateForm(category, onSubmit);
+
+ return ;
+};
+
+CategoryUpdateForm.displayName = "CategoryUpdateForm";
+export default CategoryUpdateForm;
From 06ef285d15bb70c37b4b26d2145725b9994efbd2 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Tue, 3 Nov 2020 17:49:11 +0100
Subject: [PATCH 08/23] Fix metadata
---
src/categories/components/CategoryCreatePage/form.tsx | 3 ---
src/categories/components/CategoryUpdatePage/form.tsx | 6 +++++-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/categories/components/CategoryCreatePage/form.tsx b/src/categories/components/CategoryCreatePage/form.tsx
index 7c1344a0c..0393a005a 100644
--- a/src/categories/components/CategoryCreatePage/form.tsx
+++ b/src/categories/components/CategoryCreatePage/form.tsx
@@ -55,8 +55,6 @@ function useCategoryCreateForm(
});
const {
- isMetadataModified,
- isPrivateMetadataModified,
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
@@ -69,7 +67,6 @@ function useCategoryCreateForm(
// Need to make it function to always have description.current up to date
const getData = (): CategoryCreateData => ({
...form.data,
- ...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
description: description.current
});
diff --git a/src/categories/components/CategoryUpdatePage/form.tsx b/src/categories/components/CategoryUpdatePage/form.tsx
index a98c80c1f..809b5a851 100644
--- a/src/categories/components/CategoryUpdatePage/form.tsx
+++ b/src/categories/components/CategoryUpdatePage/form.tsx
@@ -78,8 +78,12 @@ function useCategoryUpdateForm(
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
description: description.current
});
+ const getSubmitData = (): CategoryUpdateData => ({
+ ...getData(),
+ ...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified)
+ });
- const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
+ const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
return {
change: handleChange,
From b27f28c8226814482d709a811dc02c4813319b60 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Tue, 3 Nov 2020 17:49:42 +0100
Subject: [PATCH 09/23] Add rich text editor to collection pages
---
.../CollectionCreatePage.tsx | 266 ++++++++----------
.../components/CollectionCreatePage/form.tsx | 111 ++++++++
.../CollectionDetails/CollectionDetails.tsx | 19 +-
.../CollectionDetailsPage.tsx | 253 +++++++----------
.../components/CollectionDetailsPage/form.tsx | 121 ++++++++
5 files changed, 454 insertions(+), 316 deletions(-)
create mode 100644 src/collections/components/CollectionCreatePage/form.tsx
create mode 100644 src/collections/components/CollectionDetailsPage/form.tsx
diff --git a/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx b/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx
index 777ef78c7..a54235658 100644
--- a/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx
+++ b/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx
@@ -2,9 +2,8 @@ import AppHeader from "@saleor/components/AppHeader";
import { CardSpacer } from "@saleor/components/CardSpacer";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import { Container } from "@saleor/components/Container";
-import Form from "@saleor/components/Form";
import Grid from "@saleor/components/Grid";
-import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
+import Metadata from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm";
@@ -12,54 +11,21 @@ import VisibilityCard from "@saleor/components/VisibilityCard";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
import { sectionNames } from "@saleor/intl";
-import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
-import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
import React from "react";
import { useIntl } from "react-intl";
import CollectionDetails from "../CollectionDetails/CollectionDetails";
import { CollectionImage } from "../CollectionImage/CollectionImage";
-
-export interface CollectionCreatePageFormData extends MetadataFormData {
- backgroundImage: {
- url: string;
- value: string;
- };
- backgroundImageAlt: string;
- description: RawDraftContentState;
- name: string;
- slug: string;
- publicationDate: string;
- isPublished: boolean;
- seoDescription: string;
- seoTitle: string;
-}
+import CollectionCreateForm, { CollectionCreateData } from "./form";
export interface CollectionCreatePageProps {
disabled: boolean;
errors: ProductErrorFragment[];
saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void;
- onSubmit: (data: CollectionCreatePageFormData) => void;
+ onSubmit: (data: CollectionCreateData) => Promise;
}
-const initialForm: CollectionCreatePageFormData = {
- backgroundImage: {
- url: null,
- value: null
- },
- backgroundImageAlt: "",
- description: convertToRaw(ContentState.createFromText("")),
- isPublished: false,
- metadata: [],
- name: "",
- privateMetadata: [],
- publicationDate: "",
- seoDescription: "",
- seoTitle: "",
- slug: ""
-};
-
const CollectionCreatePage: React.FC = ({
disabled,
errors,
@@ -69,127 +35,121 @@ const CollectionCreatePage: React.FC = ({
}: CollectionCreatePageProps) => {
const intl = useIntl();
const localizeDate = useDateLocalize();
- const {
- makeChangeHandler: makeMetadataChangeHandler
- } = useMetadataChangeTrigger();
return (
-
+`;
+
+exports[`Storyshots Generics / Rich text editor disabled 1`] = `
+
+`;
+
+exports[`Storyshots Generics / Rich text editor error 1`] = `
+
+
@@ -31726,316 +31302,22 @@ exports[`Storyshots Views / Categories / Create category When loading 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Category Description
-
-
-
+ Category Description
+
+
+
@@ -32309,316 +31591,22 @@ exports[`Storyshots Views / Categories / Create category default 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Category Description
-
-
-
+ Category Description
+
+
+
@@ -32897,321 +31885,24 @@ exports[`Storyshots Views / Categories / Create category form errors 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Category Description
-
-
-
+ Category Description
+
+
This field is required
-
+
@@ -33484,520 +32175,22 @@ exports[`Storyshots Views / Categories / Update category default 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Category Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Category Description
+
+
+
@@ -34216,195 +32409,13 @@ Ctrl + K"
class="CardTitle-hr-id"
/>
-
-
-
+
+
-
-
-
+
+
-
-
- Category Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Category Description
+
+
This field is required
-
+
@@ -35509,195 +32992,13 @@ Ctrl + K"
class="CardTitle-hr-id"
/>
-
-
-
+
+
-
-
-
+
+
-
-
- Category Description
-
-
-
+ Category Description
+
+
+
@@ -36951,520 +33931,22 @@ exports[`Storyshots Views / Categories / Update category no background 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Category Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Category Description
+
+
+
@@ -37638,195 +34120,13 @@ Ctrl + K"
class="CardTitle-hr-id"
/>
-
-
-
+
+
-
-
-
+
+
-
-
- Category Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Category Description
+
+
+
@@ -38921,195 +34696,13 @@ Ctrl + K"
class="CardTitle-hr-id"
/>
-
-
-
+
+
-
-
-
+
+
-
-
- Category Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Category Description
+
+
+
@@ -40209,195 +35277,13 @@ Ctrl + K"
class="CardTitle-hr-id"
/>
-
-
-
+
+
-
-
-
+
+
-
-
- Category Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Category Description
+
+
+
@@ -41492,195 +35853,13 @@ Ctrl + K"
class="CardTitle-hr-id"
/>
-
-
-
+
+
-
-
-
+
+
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -44737,525 +38391,24 @@ exports[`Storyshots Views / Collections / Collection details form errors 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
This field is required
-
+
@@ -46631,316 +39784,22 @@ exports[`Storyshots Views / Collections / Collection details loading 1`] = `
class="FormSpacer-spacer-id"
/>
@@ -47751,520 +40610,22 @@ exports[`Storyshots Views / Collections / Collection details no products 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -50706,316 +43067,22 @@ exports[`Storyshots Views / Collections / Create collection default 1`] = `
class="FormSpacer-spacer-id"
/>
@@ -51547,321 +43614,24 @@ exports[`Storyshots Views / Collections / Create collection form errors 1`] = `
class="FormSpacer-spacer-id"
/>
-
+ Description
+
+
This field is required
-
+
@@ -52395,316 +44165,22 @@ exports[`Storyshots Views / Collections / Create collection loading 1`] = `
class="FormSpacer-spacer-id"
/>
@@ -112831,520 +104307,22 @@ exports[`Storyshots Views / Pages / Page details default 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Content
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Content
+
+
+
@@ -113938,520 +104916,22 @@ exports[`Storyshots Views / Pages / Page details form errors 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Content
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Content
+
+
+
@@ -115052,316 +105532,22 @@ exports[`Storyshots Views / Pages / Page details loading 1`] = `
class="FormSpacer-spacer-id"
/>
@@ -138025,316 +128211,22 @@ exports[`Storyshots Views / Products / Create product When loading 1`] = `
class="FormSpacer-spacer-id"
/>
@@ -139312,316 +129204,22 @@ exports[`Storyshots Views / Products / Create product default 1`] = `
class="FormSpacer-spacer-id"
/>
@@ -140588,316 +130186,22 @@ exports[`Storyshots Views / Products / Create product form errors 1`] = `
class="FormSpacer-spacer-id"
/>
@@ -146224,520 +135528,22 @@ exports[`Storyshots Views / Products / Product edit form errors 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -148747,520 +137553,22 @@ exports[`Storyshots Views / Products / Product edit no product attributes 1`] =
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -150995,520 +139303,22 @@ exports[`Storyshots Views / Products / Product edit no stock and no variants 1`]
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -153470,520 +141280,22 @@ exports[`Storyshots Views / Products / Product edit no stock, no variants and no
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -155882,520 +143194,22 @@ exports[`Storyshots Views / Products / Product edit no variants 1`] = `
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -158491,520 +145305,22 @@ exports[`Storyshots Views / Products / Product edit when data is fully loaded 1`
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -161004,316 +147320,22 @@ exports[`Storyshots Views / Products / Product edit when loading data 1`] = `
class="FormSpacer-spacer-id"
/>
@@ -162422,520 +148444,22 @@ exports[`Storyshots Views / Products / Product edit when product has no images 1
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
@@ -164885,520 +150409,22 @@ exports[`Storyshots Views / Products / Product edit when product has no variants
class="FormSpacer-spacer-id"
/>
-
-
- Description
-
-
-
-
-
-
-
-
-
-
-
-
-
- strikethrough
-
-
-
-
-
-
-
-
-
-
-
- blockquote
-
-
-
-
-
-
- -
-
-
-
- ol
-
-
-
-
-
-
-
-
-
-
-
-
+ Description
+
+
+
diff --git a/src/utils/richText/useRichText.test.ts b/src/utils/richText/useRichText.test.ts
index bedf3d42f..7ae0ce4ce 100644
--- a/src/utils/richText/useRichText.test.ts
+++ b/src/utils/richText/useRichText.test.ts
@@ -45,7 +45,7 @@ describe("useRichText", () => {
);
const [data, change] = hook.result.current;
- expect(data.current).toBe(undefined);
+ expect(data.current).toMatchObject({ blocks: [] });
change(fixtures.short);
From b2e6baf4e9bb2ec23c1953b39452c16c785875a1 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Fri, 6 Nov 2020 11:54:03 +0100
Subject: [PATCH 22/23] Use proper submit return type
---
.../CollectionCreatePage/CollectionCreatePage.tsx | 3 ++-
src/collections/components/CollectionCreatePage/form.tsx | 6 +++---
.../CollectionDetailsPage/CollectionDetailsPage.tsx | 3 ++-
src/pages/components/PageDetailsPage/form.tsx | 6 +++---
4 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx b/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx
index a54235658..b4fb94166 100644
--- a/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx
+++ b/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx
@@ -10,6 +10,7 @@ import SeoForm from "@saleor/components/SeoForm";
import VisibilityCard from "@saleor/components/VisibilityCard";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
+import { SubmitPromise } from "@saleor/hooks/useForm";
import { sectionNames } from "@saleor/intl";
import React from "react";
import { useIntl } from "react-intl";
@@ -23,7 +24,7 @@ export interface CollectionCreatePageProps {
errors: ProductErrorFragment[];
saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void;
- onSubmit: (data: CollectionCreateData) => Promise;
+ onSubmit: (data: CollectionCreateData) => SubmitPromise;
}
const CollectionCreatePage: React.FC = ({
diff --git a/src/collections/components/CollectionCreatePage/form.tsx b/src/collections/components/CollectionCreatePage/form.tsx
index 9cf38191b..9857022ee 100644
--- a/src/collections/components/CollectionCreatePage/form.tsx
+++ b/src/collections/components/CollectionCreatePage/form.tsx
@@ -1,7 +1,7 @@
import { OutputData } from "@editorjs/editorjs";
import { MetadataFormData } from "@saleor/components/Metadata";
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
-import useForm, { FormChange } from "@saleor/hooks/useForm";
+import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import useRichText from "@saleor/utils/richText/useRichText";
@@ -38,11 +38,11 @@ export interface UseCollectionCreateFormResult {
export interface CollectionCreateFormProps {
children: (props: UseCollectionCreateFormResult) => React.ReactNode;
- onSubmit: (data: CollectionCreateData) => Promise;
+ onSubmit: (data: CollectionCreateData) => SubmitPromise;
}
function useCollectionCreateForm(
- onSubmit: (data: CollectionCreateData) => Promise
+ onSubmit: (data: CollectionCreateData) => SubmitPromise
): UseCollectionCreateFormResult {
const [changed, setChanged] = React.useState(false);
const triggerChange = () => setChanged(true);
diff --git a/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.tsx b/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.tsx
index 4728cec71..34d214dfc 100644
--- a/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.tsx
+++ b/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.tsx
@@ -13,6 +13,7 @@ import SeoForm from "@saleor/components/SeoForm";
import VisibilityCard from "@saleor/components/VisibilityCard";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
+import { SubmitPromise } from "@saleor/hooks/useForm";
import { sectionNames } from "@saleor/intl";
import React from "react";
import { useIntl } from "react-intl";
@@ -35,7 +36,7 @@ export interface CollectionDetailsPageProps extends PageListProps, ListActions {
onImageDelete: () => void;
onImageUpload: (file: File) => void;
onProductUnassign: (id: string, event: React.MouseEvent) => void;
- onSubmit: (data: CollectionUpdateData) => Promise;
+ onSubmit: (data: CollectionUpdateData) => SubmitPromise;
}
const CollectionDetailsPage: React.FC = ({
diff --git a/src/pages/components/PageDetailsPage/form.tsx b/src/pages/components/PageDetailsPage/form.tsx
index 9a035ff18..9a71a2a55 100644
--- a/src/pages/components/PageDetailsPage/form.tsx
+++ b/src/pages/components/PageDetailsPage/form.tsx
@@ -1,7 +1,7 @@
import { OutputData } from "@editorjs/editorjs";
import { MetadataFormData } from "@saleor/components/Metadata";
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
-import useForm, { FormChange } from "@saleor/hooks/useForm";
+import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
import { PageDetails_page } from "@saleor/pages/types/PageDetails";
import getPublicationData from "@saleor/utils/data/getPublicationData";
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
@@ -38,12 +38,12 @@ export interface UsePageUpdateFormResult {
export interface PageFormProps {
children: (props: UsePageUpdateFormResult) => React.ReactNode;
page: PageDetails_page;
- onSubmit: (data: PageData) => Promise;
+ onSubmit: (data: PageData) => SubmitPromise;
}
function usePageForm(
page: PageDetails_page,
- onSubmit: (data: PageData) => Promise
+ onSubmit: (data: PageData) => SubmitPromise
): UsePageUpdateFormResult {
const [changed, setChanged] = React.useState(false);
const triggerChange = () => setChanged(true);
From c60d719f218dc4ed6434b3432eb7ce6d45212027 Mon Sep 17 00:00:00 2001
From: dominik-zeglen
Date: Fri, 6 Nov 2020 12:19:37 +0100
Subject: [PATCH 23/23] Update messages
---
locale/defaultMessages.json | 22 ----------------------
1 file changed, 22 deletions(-)
diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json
index 414af4209..d786531cc 100644
--- a/locale/defaultMessages.json
+++ b/locale/defaultMessages.json
@@ -1785,28 +1785,6 @@
"src_dot_components_dot_RadioGroupField_dot_4205644805": {
"string": "No results found"
},
- "src_dot_components_dot_RichTextEditor_dot_1603794322": {
- "context": "dialog header",
- "string": "Add Image Link"
- },
- "src_dot_components_dot_RichTextEditor_dot_2049070632": {
- "context": "replace image, button",
- "string": "Replace"
- },
- "src_dot_components_dot_RichTextEditor_dot_2160163587": {
- "context": "button",
- "string": "Add or Edit Link"
- },
- "src_dot_components_dot_RichTextEditor_dot_286109898": {
- "context": "rich text error",
- "string": "Invalid content"
- },
- "src_dot_components_dot_RichTextEditor_dot_2925475978": {
- "string": "URL Linked"
- },
- "src_dot_components_dot_RichTextEditor_dot_4035057905": {
- "string": "Image URL"
- },
"src_dot_components_dot_RowNumberSelect_dot_1154361791": {
"string": "No of Rows:"
},