diff --git a/example/src/App.tsx b/example/src/App.tsx index 69d2366..48e0dfb 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -110,5 +110,21 @@ const blockInitalValue2 = [ }, }, }, + { + block: { + _id: '5', + type: 'image', + properties: { + document: [ + { + text: '', + properties: [ + 'https://repository-images.githubusercontent.com/125159715/61f2ee00-865a-11e9-8ce5-f7028c561633', + ], + }, + ], + }, + }, + }, ]; export default App; diff --git a/src/Editor.tsx b/src/Editor.tsx index 39bdfdc..ac954a8 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -12,6 +12,8 @@ import { withLinks } from './Helpers/LinkHelper'; import Element from './plugins/Element'; import Leaf from './plugins/Leaf'; import withBlockID from './plugins/withBlockID'; +import withImages from './plugins/withImages'; +import withBreak from './plugins/withBreak'; import serialize from './serialize/index'; import deserialize from './deserialize/index'; import { ADD, UPDATE, DELETE } from './constant/operations'; @@ -40,7 +42,7 @@ const Editor: (props: Props) => any = ({ isHoveringToolBar = false, }) => { const [editorData, setData] = useState(EMPTY_NODE); - const withPlugins = [withReact, withHistory, withLinks, withBlockID] as const; + const withPlugins = [withReact, withHistory, withLinks, withBlockID, withImages, withBreak] as const; const editor: any = useMemo(() => pipe(createEditor(), ...withPlugins), []); const renderElement = useCallback((props) => , []); const renderLeaf = useCallback((props) => , []); diff --git a/src/Helpers/ImageHelper.tsx b/src/Helpers/ImageHelper.tsx new file mode 100644 index 0000000..4089d5f --- /dev/null +++ b/src/Helpers/ImageHelper.tsx @@ -0,0 +1,33 @@ +import React, { FC, SVGProps } from 'react'; +import { Transforms } from 'slate'; +import { ReactEditor, useSlate } from 'slate-react'; + +import { Button, Icon } from './Helper'; + +export const insertImage = (editor: ReactEditor, url: string) => { + const text = { text: '' }; + const image = { type: 'image', url, children: [text] }; + Transforms.insertNodes(editor, image); +}; + +interface InsertImageButtonProps { + icon: FC>; + iconColor?: string; +} +export const InsertImageButton: (props: InsertImageButtonProps) => JSX.Element = ({ icon, iconColor = '#ffffff' }) => { + const editor = useSlate(); + return ( + + ); +}; diff --git a/src/ToolBar/ToolBar.tsx b/src/ToolBar/ToolBar.tsx index bcf5e3c..a3632ec 100644 --- a/src/ToolBar/ToolBar.tsx +++ b/src/ToolBar/ToolBar.tsx @@ -7,6 +7,7 @@ import { BlockButton } from '../Helpers/BlockHelper'; import { MarkButton } from '../Helpers/MarkHelper'; import { LinkButton, insertLink } from '../Helpers/LinkHelper'; import { Menu, Portal, LinkInput } from '../Helpers/Helper'; +import { InsertImageButton } from '../Helpers/ImageHelper'; // icons import { ReactComponent as Bold } from '../assets/bold.svg'; @@ -18,6 +19,7 @@ import { ReactComponent as Underline } from '../assets/underline.svg'; import { ReactComponent as H1 } from '../assets/h1.svg'; import { ReactComponent as H2 } from '../assets/h2.svg'; import { ReactComponent as CLEAR_FORMAT } from '../assets/clear_format.svg'; +import { ReactComponent as Image } from '../assets/image.svg'; const ICON_COLOR = '#b5b9c6'; export const FixedMenu: any = React.forwardRef(({ ...props }, ref: React.Ref) => ( @@ -113,6 +115,7 @@ export const ToolBar: React.FC = () => { + diff --git a/src/assets/image.svg b/src/assets/image.svg new file mode 100644 index 0000000..b711525 --- /dev/null +++ b/src/assets/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/deserialize/index.ts b/src/deserialize/index.ts index 8e151aa..06b27ad 100644 --- a/src/deserialize/index.ts +++ b/src/deserialize/index.ts @@ -56,6 +56,10 @@ const deserialization = (blockContentList: any) => { const { block } = blockContent; const children = getChildNodes(block); const type = getNodeType(block.type); + if (type === 'image' && block.properties) { + return { id: block._id, type, children, url: block.properties.document[0].properties }; + } + return { id: block._id, type, children }; }); return deserializedContent; diff --git a/src/plugins/Element.tsx b/src/plugins/Element.tsx index e2e913f..459a0d0 100644 --- a/src/plugins/Element.tsx +++ b/src/plugins/Element.tsx @@ -10,6 +10,7 @@ import { Rectangle, Heading1, Heading2, + Image, } from './ElementStyle'; import getShortLink from '../utils/getShortLink'; @@ -41,6 +42,15 @@ const Element: React.FC = ({ attributes, children, element } ); + case `image`: + return ( +
+
+ +
+ {children} +
+ ); default: return

{children}

; } diff --git a/src/plugins/ElementStyle.ts b/src/plugins/ElementStyle.ts index ee372c5..de88d12 100644 --- a/src/plugins/ElementStyle.ts +++ b/src/plugins/ElementStyle.ts @@ -79,3 +79,9 @@ export const Heading1 = styled.h1` export const Heading2 = styled.h2` color: ${({ theme }) => (theme.colors !== undefined ? theme.colors.text : '#050b21')}; `; +export const Image = styled.img` + display: block; + max-width: 100%; + max-height: 20em; + padding: 4px 0px 0px 20px; +`; diff --git a/src/plugins/withBreak.tsx b/src/plugins/withBreak.tsx new file mode 100644 index 0000000..f133692 --- /dev/null +++ b/src/plugins/withBreak.tsx @@ -0,0 +1,18 @@ +import { Transforms } from 'slate'; + +const withBreak = (editor: any) => { + const localEditor = editor; + localEditor.insertBreak = () => { + const newLine = { + type: 'paragraph', + children: [ + { + text: '', + }, + ], + }; + Transforms.insertNodes(editor, newLine); + }; + return localEditor; +}; +export default withBreak; diff --git a/src/plugins/withImages.tsx b/src/plugins/withImages.tsx new file mode 100644 index 0000000..a8716d0 --- /dev/null +++ b/src/plugins/withImages.tsx @@ -0,0 +1,11 @@ +import { ReactEditor } from 'slate-react'; + +const withImages = (editor: ReactEditor) => { + const { isVoid } = editor; + const localEditor = editor; + localEditor.isVoid = (element) => { + return element.type === 'image' ? true : isVoid(element); + }; + return localEditor; +}; +export default withImages; diff --git a/src/serialize/index.tsx b/src/serialize/index.tsx index 2211ca8..54fc60f 100644 --- a/src/serialize/index.tsx +++ b/src/serialize/index.tsx @@ -38,7 +38,12 @@ const checkIdAndReturnBlock = (type: string, document: any, node: any) => { const serializeParagraph = (paragraphNode: any, textType: string) => { const document = paragraphNode.children.map((childNodes: any) => { const text = getParagraphText(childNodes); - const properties = getParagraphProperties(childNodes); + let properties = []; + if (textType === 'image') { + properties.push(paragraphNode.url); + } else { + properties = getParagraphProperties(childNodes); + } return { text, properties }; }); const paragraphBlock = checkIdAndReturnBlock(textType, document, paragraphNode); @@ -64,6 +69,8 @@ const serialize = (slateNodesList: any) => { case 'heading-one': case 'heading-two': return serializeHeading(node, node.type); + case 'image': + return serializeParagraph(node, 'image'); default: return serializeParagraph(node, 'text'); }