Skip to content

Commit

Permalink
Merge pull request #43 from niuware/development
Browse files Browse the repository at this point in the history
Add custom atomic blocks feature
  • Loading branch information
niuware authored Oct 23, 2019
2 parents 1faadc2 + 8a5a864 commit 19a81c5
Show file tree
Hide file tree
Showing 8 changed files with 357 additions and 64 deletions.
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ The Material-UI Rich Text Editor and Viewer

<img src="http://niuware.github.io/public/assets/mui-rte/editor-w-controls-1-2-0.png" width="600" />

**mui-rte** is a complete text editor and viewer for `material-ui` v3 and v4 based on `draft-js` and written in Typescript. It is ready to use out of the box yet supports user defined block, style, callback, and decorator definitions as well as toolbar and theme customization to enhance the editor to all needs.
**mui-rte** is a complete text editor and viewer for `material-ui` v3 and v4 based on `draft-js` and written in Typescript. It is ready to use out of the box yet supports user defined blocks, styles, callbacks, and decorators as well as toolbar and theme customization to enhance the editor to all needs.

## Installation

Expand All @@ -28,7 +28,7 @@ ReactDOM.render(
)
```

You can load default content as follows:
You can load default content as the following example. The value should be a stringified `RawDraftContentState` object:

```js
import MUIRichTextEditor from 'mui-rte'
Expand All @@ -50,11 +50,11 @@ Check the [examples](https://github.com/niuware/mui-rte/tree/master/examples) di

## Custom Controls

You can define your custom inline styles, block styles and callback actions to the editor. Just select an icon from `@material-ui/icons` and define your rules.
You can define your custom inline styles, blocks, atomic blocks and callback actions to the editor. Just select an icon from `@material-ui/icons` and define your rules.

### Adding a custom inline style

This sample adds a control to change the background color and font color of the selected text:
This sample adds a control to change the background color and font color of the typed or selected text:

```js
import MUIRichTextEditor from 'mui-rte'
Expand All @@ -76,7 +76,7 @@ import InvertColorsIcon from '@material-ui/icons/InvertColors'
/>
```

### Adding a custom block style
### Adding a custom block

This sample adds a block to the editor based on a `React Element` defined:

Expand Down Expand Up @@ -109,6 +109,10 @@ const MyBlock = (props) => {
/>
```

### Adding a custom atomic block

Check [this sample](https://github.com/niuware/mui-rte/blob/master/examples/atomic-custom-block/index.tsx) that shows how to create a control to add a `@material-ui` Card component to the editor.

### Adding a custom callback control

This sample adds a control that will trigger a custom callback function:
Expand Down Expand Up @@ -241,11 +245,12 @@ Object.assign(defaultTheme, {
|---|---|---|---|
|id|`string`|optional|The HTML id attribute for the control|
|name|`string`|required|The name of the custom control. For rendering the control this name should be added to the `MUIRichTextEditor` `controls` property.|
|icon|`JSX.Element`|required|The `@material-ui/icons` icon for the control. [Check this](https://material.io/resources/icons/?style=baseline) for available icons.|
|type|`string`|required|Either "inline", "block" or "callback"|
|inlineStyle|`string`|optional|The `React.CSSProperties` object for styling the text when using the custom inline style.|
|blockWrapper|`React.ReactElement`|optional|The custom React component used for rendering the custom block.|
|onClick|`(editorState: EditorState, name: string) => void`|optional|The callback function triggered when the custom control is clicked.|
|icon|`JSX.Element`|optional|The `@material-ui/icons` icon for the control. For "atomic" control type, the icon is not required. [Check this](https://material.io/resources/icons/?style=baseline) for available icons.|
|type|`string`|required|Either "inline", "block", "atomic" or "callback"|
|inlineStyle|`string`|optional|The `React.CSSProperties` object for styling the text when using a custom inline style.|
|blockWrapper|`React.ReactElement`|optional|The custom React component used for rendering a custom block.|
|atomicComponent|`React.FunctionComponent`|optional|The custom React FunctionComponent used for rendering a custom atomic block.|
|onClick|`(editorState: EditorState, name: string, anchor: HTMLElement | null) => void`|optional|The callback function triggered when the custom control is clicked. The received arguments include the current `EditorState` object, the name of the clicked control and the `HTMLElement` from which the click was raised. |

<br />

Expand Down
256 changes: 256 additions & 0 deletions examples/atomic-custom-block/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import React, { useRef, useState, FunctionComponent, useEffect } from 'react'
import MUIRichTextEditor from '../..'
import { Card, CardHeader, Avatar, CardMedia, CardContent,
Typography, IconButton, CardActions, Grid } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles'
import Popover from '@material-ui/core/Popover'
import TextField from '@material-ui/core/TextField'
import Button from '@material-ui/core/Button'
import WebAssetIcon from '@material-ui/icons/WebAsset'
import ShareIcon from '@material-ui/icons/Share'
import FavoriteIcon from '@material-ui/icons/Favorite'
import DoneIcon from '@material-ui/icons/Done'
import CloseIcon from '@material-ui/icons/Close'

const cardPopverStyles = makeStyles({
root: {
padding: 10,
maxWidth: 350
},
textField: {
width: "100%"
}
})

const cardStyles = makeStyles({
root: {
maxWidth: 345
},
media: {
height: 0,
paddingTop: '56.25%'
},
avatar: {
backgroundColor: "tomato"
}
})

const save = (data: string) => {
console.log(data)
}

type TMyCardData = {
title?: string
name?: string
date?: Date
text?: string
image?: string
}

type TAnchor = HTMLElement | null

const MyCard: FunctionComponent<any> = (props) => {
const { blockProps } = props
const classes = cardStyles(props)

const handleLiked = () => {
alert("Favorited")
}

const handleShared = () => {
alert("Shared")
}

return (
<Card className={classes.root}>
<CardHeader
avatar={
<Avatar aria-label="name" className={classes.avatar}>
{blockProps.name && blockProps.name.substring(0, 1)}
</Avatar>
}
title={blockProps.title}
subheader={blockProps.date && blockProps.date.toLocaleDateString()}
/>
<CardMedia
className={classes.media}
image={blockProps.image || "default"}
title={blockProps.title}
/>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
{blockProps.text}
</Typography>
</CardContent>
<CardActions disableSpacing>
<IconButton
aria-label="like card"
onClick={handleLiked}>
<FavoriteIcon />
</IconButton>
<IconButton
aria-label="share"
onClick={handleShared}
>
<ShareIcon />
</IconButton>
</CardActions>
</Card>
)
}

interface IMyCardPopoverProps {
anchor: TAnchor
onSubmit: (data: TMyCardData, insert: boolean) => void
}

type TMyCardPopoverState = {
anchor: TAnchor
isCancelled: boolean
}

const MyCardPopover: FunctionComponent<IMyCardPopoverProps> = (props) => {
const classes = cardPopverStyles(props)
const [state, setState] = useState<TMyCardPopoverState>({
anchor: null,
isCancelled: false
})
const [data, setData] = useState<TMyCardData>({})

useEffect(() => {
setState({
anchor: props.anchor,
isCancelled: false
})
setData({
date: new Date()
})
}, [props.anchor])

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setData({
...data,
[event.target.name]: event.target.value
})
}

const textFieldProps = {
className: classes.textField,
onChange: handleChange,
InputLabelProps: {
shrink: true
}
}

return (
<Popover
anchorEl={state.anchor}
open={state.anchor !== null}
onExited={() => {
props.onSubmit(data, !state.isCancelled)
}}
anchorOrigin={{
vertical: "bottom",
horizontal: "right"
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<Grid container spacing={1} className={classes.root}>
<Grid item xs={6}>
<TextField
{...textFieldProps}
autoFocus={true}
label="Title"
name="title"
/>
</Grid>
<Grid item xs={6}>
<TextField
{...textFieldProps}
label="Name"
name="name"
/>
</Grid>
<Grid item xs={12}>
<TextField
{...textFieldProps}
label="Text"
name="text"
/>
</Grid>
<Grid item xs={12}>
<TextField
{...textFieldProps}
label="Image"
name="image"
/>
</Grid>
<Grid item container xs={12} justify="flex-end">
<Button onClick={() => {
setState({
anchor: null,
isCancelled: true
})
}}
>
<CloseIcon />
</Button>
<Button onClick={() => {
setState({
anchor: null,
isCancelled: false
})
}}
>
<DoneIcon />
</Button>
</Grid>
</Grid>
</Popover>
)
}

const AtomicCustomBlock: FunctionComponent = (props) => {

const ref = useRef()
const [anchor, setAnchor] = useState<HTMLElement | null>(null)
return (
<>
<MyCardPopover
anchor={anchor}
onSubmit={(data, insert) => {
if (insert) {
(ref as any).current.insertAtomicBlock("my-card", data)
}
setAnchor(null)
}}
/>
<MUIRichTextEditor
label="Type something here..."
ref={ref}
onSave={save}
controls={["title", "bold", "underline", "add-card", "save"]}
customControls={[
{
name: "my-card",
type: "atomic",
atomicComponent: MyCard
},
{
name: "add-card",
icon: <WebAssetIcon />,
type: "callback",
onClick: (editorState, name, anchor) => {
setAnchor(anchor)
}
}
]}
/>
</>
)
}

export default AtomicCustomBlock
2 changes: 2 additions & 0 deletions examples/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import InlineToolbar from './inline-toolbar'
import CustomInlineToolbar from './custom-inline-toolbar'
import LoadHTML from './load-html'
import ResetValue from './reset-value'
import AtomicCustomBlock from './atomic-custom-block'

const App = () => {

Expand All @@ -34,6 +35,7 @@ const App = () => {
<button onClick={() => setSample(<CustomInlineToolbar />)}>Custom Inline Toolbar</button>
<button onClick={() => setSample(<LoadHTML />)}>Load from HTML</button>
<button onClick={() => setSample(<ResetValue />)}>Reset value</button>
<button onClick={() => setSample(<AtomicCustomBlock />)}>Atomic Custom Block</button>
{sample}
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mui-rte",
"version": "1.7.0",
"version": "1.8.0",
"description": "Material-UI Rich Text Editor and Viewer",
"keywords": [
"material-ui",
Expand Down
Loading

0 comments on commit 19a81c5

Please sign in to comment.