Merge pull request #395 from mirumee/fix/rich-text-error

Handle rich text editor content error
This commit is contained in:
Dominik Żegleń 2020-03-03 11:33:59 +01:00 committed by GitHub
commit 4c8cce4f23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 213 additions and 192 deletions

View file

@ -36,6 +36,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Remove PO files from repo and update translations #409 by @dominik-zeglen
- Add optional chaining and explicitely return "Not found" page - #408 by @dominik-zeglen
- Do not store errors in form component - #410 by @dominik-zeglen
- Handle rich text editor content error - #395 by @dominik-zeglen
## 2.0.0

View file

@ -1267,6 +1267,10 @@
"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"
},

View file

@ -1,6 +1,7 @@
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import Typography from "@material-ui/core/Typography";
import { FormattedMessage } from "react-intl";
import classNames from "classnames";
import { RawDraftContentState } from "draft-js";
import {
@ -13,6 +14,8 @@ import isEqual from "lodash-es/isEqual";
import React from "react";
import { ChangeEvent } from "@saleor/hooks/useForm";
import ErrorBoundary from "react-error-boundary";
import { CreateCSSProperties } from "@material-ui/styles/withStyles";
import BoldIcon from "../../icons/BoldIcon";
import HeaderTwo from "../../icons/HeaderTwo";
import HeaderThree from "../../icons/HeaderThree";
@ -38,158 +41,165 @@ export interface RichTextEditorProps {
}
const useStyles = makeStyles(
theme => ({
error: {
color: theme.palette.error.main
},
helperText: {
marginTop: theme.spacing(0.75)
},
input: {
position: "relative"
},
label: {
fontSize: theme.typography.caption.fontSize,
left: 12,
position: "absolute",
top: 9
},
linkIcon: {
marginTop: 2
},
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
}
theme => {
const editorContainer: CreateCSSProperties = {
border: `1px ${theme.palette.divider} solid`,
borderRadius: 4,
padding: "27px 12px 10px",
position: "relative",
transition: theme.transitions.duration.shortest + "ms"
};
return {
editorContainer,
error: {
color: theme.palette.error.main
},
"& .Draftail": {
"&-Editor": {
"&--focus": {
boxShadow: `inset 0px 0px 0px 2px ${theme.palette.primary.main}`
helperText: {
marginTop: theme.spacing(0.75)
},
input: {
position: "relative"
},
label: {
fontSize: theme.typography.caption.fontSize,
left: 12,
position: "absolute",
top: 9
},
linkIcon: {
marginTop: 2
},
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"
},
"&:hover": {
borderColor: theme.palette.primary.main
},
border: `1px ${theme.palette.divider} solid`,
borderRadius: 4,
padding: "27px 12px 10px",
position: "relative",
transition: theme.transitions.duration.shortest + "ms"
},
"&-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)
},
alignItems: "center",
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"
"&-root": {
...theme.typography.body1
}
},
"&-block": {
"&--blockquote": {
borderLeft: `2px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing(1, 2)
}
}
},
"&$error": {
"& .Draftail": {
"&-Editor": {
borderColor: theme.palette.error.main
"&--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)
},
alignItems: "center",
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)
}
}
},
"&$error": {
"& .Draftail": {
"&-Editor": {
borderColor: theme.palette.error.main
}
}
}
}
},
scroll: {
"& .DraftEditor": {
"&-editorContainer": {
"& .public-DraftEditor-content": {
lineHeight: 1.62
},
scroll: {
"& .DraftEditor": {
"&-editorContainer": {
"& .public-DraftEditor-content": {
lineHeight: 1.62
}
}
}
},
smallIcon: {
marginLeft: 10
}
},
smallIcon: {
marginLeft: 10
}
}),
};
},
{ name: "RichTextEditor" }
);
@ -226,58 +236,64 @@ const RichTextEditor: React.FC<RichTextEditorProps> = props => {
<Typography className={classes.label} variant="caption" color="primary">
{label}
</Typography>
<DraftailEditor
key={JSON.stringify(initial)}
rawContentState={
initial && Object.keys(initial).length > 0 ? initial : null
}
onSave={value => handleSave(value, initial, name, onChange)}
blockTypes={[
{
icon: <HeaderOne />,
type: BLOCK_TYPE.HEADER_ONE
},
{ icon: <HeaderTwo />, type: BLOCK_TYPE.HEADER_TWO },
{ icon: <HeaderThree />, type: BLOCK_TYPE.HEADER_THREE },
{ icon: <QuotationIcon />, type: BLOCK_TYPE.BLOCKQUOTE },
{
icon: <UnorderedListIcon />,
type: BLOCK_TYPE.UNORDERED_LIST_ITEM
},
{ icon: <OrderedListIcon />, type: BLOCK_TYPE.ORDERED_LIST_ITEM }
]}
inlineStyles={[
{
icon: <BoldIcon className={classes.smallIcon} />,
type: INLINE_STYLE.BOLD
},
{
icon: <ItalicIcon className={classes.smallIcon} />,
type: INLINE_STYLE.ITALIC
},
{
icon: <StrikethroughIcon />,
type: INLINE_STYLE.STRIKETHROUGH
<ErrorBoundary
FallbackComponent={() => (
<div className={classes.editorContainer}>
<Typography color="error">
<FormattedMessage
defaultMessage="Invalid content"
description="rich text error"
/>
</Typography>
</div>
)}
>
<DraftailEditor
key={JSON.stringify(initial)}
rawContentState={
initial && Object.keys(initial).length > 0 ? initial : null
}
]}
enableLineBreak
entityTypes={[
{
attributes: ["url"],
decorator: LinkEntity,
icon: <LinkIcon className={classes.linkIcon} />,
source: LinkSource,
type: ENTITY_TYPE.LINK
}
// {
// attributes: ["href"],
// decorator: ImageEntity,
// icon: <ImageIcon />,
// source: ImageSource,
// type: ENTITY_TYPE.IMAGE
// }
]}
/>
onSave={value => handleSave(value, initial, name, onChange)}
blockTypes={[
{
icon: <HeaderOne />,
type: BLOCK_TYPE.HEADER_ONE
},
{ icon: <HeaderTwo />, type: BLOCK_TYPE.HEADER_TWO },
{ icon: <HeaderThree />, type: BLOCK_TYPE.HEADER_THREE },
{ icon: <QuotationIcon />, type: BLOCK_TYPE.BLOCKQUOTE },
{
icon: <UnorderedListIcon />,
type: BLOCK_TYPE.UNORDERED_LIST_ITEM
},
{ icon: <OrderedListIcon />, type: BLOCK_TYPE.ORDERED_LIST_ITEM }
]}
inlineStyles={[
{
icon: <BoldIcon className={classes.smallIcon} />,
type: INLINE_STYLE.BOLD
},
{
icon: <ItalicIcon className={classes.smallIcon} />,
type: INLINE_STYLE.ITALIC
},
{
icon: <StrikethroughIcon />,
type: INLINE_STYLE.STRIKETHROUGH
}
]}
enableLineBreak
entityTypes={[
{
attributes: ["url"],
decorator: LinkEntity,
icon: <LinkIcon className={classes.linkIcon} />,
source: LinkSource,
type: ENTITY_TYPE.LINK
}
]}
/>
</ErrorBoundary>
</div>
{helperText && (
<Typography