Skip to content

Commit

Permalink
Enable atomic component with autocomplete (niuware#115)
Browse files Browse the repository at this point in the history
* Add property to autocomplete strategy type

* Provide editor state instance when inserting atomic block

* Update logic to add atomic control with autocomplete

* Add autocomplete atomic example

* Update package version

* Update README
  • Loading branch information
niuware authored May 4, 2020
1 parent 9708996 commit c84ad18
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 22 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ import { EditorState } from 'draft-js'

You can define autocomplete strategies to present suggested content lists based on the text input. Just set your trigger character, add some search keys and the content to insert and the editor will do everything for you. You can navigate through suggestions using the keyboard arrows and finally press 'Enter' to insert your content into the editor.

### Emoji strategy example
### Simple strategy example

This is a simple example to present emoji suggestions when the user start typing a text like ':face', ':joy', or ':grin':
This is an example to show emoji suggestions when the user start typing a text like ':face', ':joy', or ':grin':

```js
import MUIRichTextEditor from 'mui-rte'
Expand Down Expand Up @@ -183,7 +183,11 @@ const emojis = [
/>
```

Check [this sample](https://github.com/niuware/mui-rte/blob/master/examples/autocomplete/index.tsx) that shows how to add multiple autocomplete strategies.
Check [this sample](https://github.com/niuware/mui-rte/blob/master/examples/autocomplete/index.tsx) that shows how to add multiple autocomplete strategies to a single editor.

### Atomic strategy example

Check [this sample](https://github.com/niuware/mui-rte/blob/master/examples/autocomplete/index.tsx) that shows how to combine atomic custom controls with the autocomplete strategy feature.

## Custom Decorators

Expand Down Expand Up @@ -365,6 +369,7 @@ Object.assign(defaultTheme, {
|triggerChar|`string`|required|A single character that triggers the autocomplete strategy.|
|items|`TAutocompleteItem[]`|required|List of autocomplete suggestion items.|
|insertSpaceAfter|`boolean`|optional|If `false` it won't add an space after inserting the content into the editor. Default is `true`.|
|atomicBlockName|`string`|optional|Use an *atomic* custom control type to add the content to the editor.|

<br />

Expand All @@ -373,7 +378,7 @@ Object.assign(defaultTheme, {
|Property|Type||description|
|---|---|---|---|
|keys|`string[]`|required|The list of keys that the user needs to type to reveal this item suggestion.|
|value|`string`|required|The value to insert into the editor when the item is selected.|
|value|`any`|required|The value to insert into the editor when the item is selected.|
|content|`string | JSX.Element`|required|The content presented in the autocomplete suggestion list for this item. Note that this content is render under a `ListItem` component.|

<br />
Expand Down
88 changes: 88 additions & 0 deletions examples/autocomplete-atomic/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { FunctionComponent } from 'react'
import Avatar from '@material-ui/core/Avatar'
import Chip from '@material-ui/core/Chip'
import MUIRichTextEditor from '../../'
import { TAutocompleteItem } from '../../src/components/Autocomplete'

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

const cities: TAutocompleteItem[] = [
{
keys: ["mexico"],
value: {
name: "Mexico City",
image: "🇲🇽"
},
content: "Mexico City",
},
{
keys: ["mexico", "beach"],
value: {
name: "Cancun",
image: "🚩"
},
content: "Cancun",
},
{
keys: ["japan", "olympics"],
value: {
name: "Tokyo",
image: "🇯🇵"
},
content: "Tokyo",
},
{
keys: ["japan"],
value: {
name: "Osaka",
image: "🏁"
},
content: "Osaka",
}
]

const CityChip: FunctionComponent<any> = (props) => {
const { blockProps } = props
const { value } = blockProps // Get the value provided in the TAutocompleteItem[]

const handleClick = () => {
console.log(value.name)
}

return (
<Chip
avatar={<Avatar>{value.image}</Avatar>}
label={value.name}
onClick={handleClick}
/>
)
}

const AutocompleteAtomic = () => {
return (
<MUIRichTextEditor
label="Try typing '/mexico'..."
onSave={save}
customControls={[
{
name: "my-city",
type: "atomic",
atomicComponent: CityChip
},
]}
autocomplete={{
strategies: [
{
items: cities,
triggerChar: "/",
atomicBlockName: "my-city"
}
]
}}
/>
)
}

export default AutocompleteAtomic
2 changes: 2 additions & 0 deletions examples/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import AtomicCustomBlock from './atomic-custom-block'
import KeyBindings from './key-bindings'
import MaxLength from './max-length'
import Autocomplete from './autocomplete'
import AutocompleteAtomic from './autocomplete-atomic'

const App = () => {

Expand Down Expand Up @@ -42,6 +43,7 @@ const App = () => {
<button onClick={() => setSample(<KeyBindings />)}>Key Bindings</button>
<button onClick={() => setSample(<MaxLength />)}>Max length</button>
<button onClick={() => setSample(<Autocomplete />)}>Autocomplete</button>
<button onClick={() => setSample(<AutocompleteAtomic />)}>Autocomplete Atomic</button>
<div style={{
margin: "20px 0"
}}>
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.17.2",
"version": "1.17.3",
"description": "Material-UI Rich Text Editor and Viewer",
"keywords": [
"material-ui",
Expand Down
50 changes: 34 additions & 16 deletions src/MUIRichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export type TAutocompleteStrategy = {
triggerChar: string
items: TAutocompleteItem[]
insertSpaceAfter?: boolean
atomicBlockName?: string
}

export type TAutocomplete = {
Expand Down Expand Up @@ -386,20 +387,37 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
'focusOffset': currentSelection.getFocusOffset() + searchTerm.length + 1
})
const currentContentState = editorState.getCurrentContent()
const entityKey = currentContentState.createEntity('AC_ITEM', 'IMMUTABLE').getLastCreatedEntityKey();
const contentState = Modifier.replaceText(editorStateRef.current!.getCurrentContent(),
newSelection,
item.value,
editorStateRef.current!.getCurrentInlineStyle(),
entityKey)
const newEditorState = EditorState.push(editorStateRef.current!, contentState, "insert-characters")
if (currentAutocompleteRef.current!.insertSpaceAfter === false) {
handleChange(newEditorState)
if (currentAutocompleteRef.current!.atomicBlockName) {
const name = currentAutocompleteRef.current!.atomicBlockName
const block = atomicBlockExists(name, props.customControls)
if (block) {
const contentState = Modifier.removeRange(editorStateRef.current!.getCurrentContent(),
newSelection,
"forward")
const newEditorState = EditorState.push(editorStateRef.current!, contentState, "remove-range")
const withAtomicBlock = insertAtomicBlock(newEditorState, name.toUpperCase(), {
value: item.value
}, {
selection: newEditorState.getCurrentContent().getSelectionAfter()
})
handleChange(withAtomicBlock)
}
} else {
const addSpaceState = Modifier.insertText(newEditorState.getCurrentContent(),
newEditorState.getSelection(), " ",
newEditorState.getCurrentInlineStyle())
handleChange(EditorState.push(newEditorState, addSpaceState, "insert-characters"))
const entityKey = currentContentState.createEntity("AC_ITEM", 'IMMUTABLE').getLastCreatedEntityKey();
const contentState = Modifier.replaceText(editorStateRef.current!.getCurrentContent(),
newSelection,
item.value,
editorStateRef.current!.getCurrentInlineStyle(),
entityKey)
const newEditorState = EditorState.push(editorStateRef.current!, contentState, "insert-characters")
if (currentAutocompleteRef.current!.insertSpaceAfter === false) {
handleChange(newEditorState)
} else {
const addSpaceState = Modifier.insertText(newEditorState.getCurrentContent(),
newEditorState.getSelection(), " ",
newEditorState.getCurrentInlineStyle())
handleChange(EditorState.push(newEditorState, addSpaceState, "insert-characters"))
}
}
}
handleAutocompleteClosed()
Expand Down Expand Up @@ -476,7 +494,7 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
if (!block) {
return
}
const newEditorState = insertAtomicBlock(block.name.toUpperCase(), data, {
const newEditorState = insertAtomicBlock(editorState, block.name.toUpperCase(), data, {
selection: editorState.getCurrentContent().getSelectionAfter()
})
updateStateForPopover(newEditorState)
Expand Down Expand Up @@ -711,7 +729,7 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
updateStateForPopover(EditorState.forceSelection(newEditorState, newEditorState.getCurrentContent().getSelectionAfter()))
}
else {
const newEditorState = insertAtomicBlock("IMAGE", data)
const newEditorState = insertAtomicBlock(editorState, "IMAGE", data)
updateStateForPopover(EditorState.forceSelection(newEditorState, newEditorState.getCurrentContent().getSelectionAfter()))
}
setFocusMediaKey("")
Expand Down Expand Up @@ -794,7 +812,7 @@ const MUIRichTextEditor: RefForwardingComponent<any, IMUIRichTextEditorProps> =
return null
}

const insertAtomicBlock = (type: string, data: any, options?: any) => {
const insertAtomicBlock = (editorState: EditorState, type: string, data: any, options?: any) => {
const contentState = editorState.getCurrentContent()
const contentStateWithEntity = contentState.createEntity(type, 'IMMUTABLE', data)
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
Expand Down
2 changes: 1 addition & 1 deletion src/components/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createStyles, withStyles, WithStyles } from '@material-ui/core/styles'

export type TAutocompleteItem = {
keys: string[]
value: string
value: any
content: string | JSX.Element
}

Expand Down

0 comments on commit c84ad18

Please sign in to comment.