Background:
Using Draft-JS, I've a chunk of code which will replace a word with an Entity on clicking 'Return' so user types in 'i love eggplant' then 'Return' they should see 'i love :eggplant:'
Problem
After the entity has been added, when I do an undo (ctrl z
, nothing fancy), it removes all of my sentence rather than just the entity. Based on what I've read about Draft-JS I would have expected it to revert to 'i love eggplant' which is the desired effect.
Links
- My Example (works as described above) - https://codepen.io/anon/pen/VMpyPM
- Link Example (works as expected using button click) - https://codepen.io/Kiwka/pen/ZLvPeO
Code
This code is very stripped down from the full code for readability, yet demos the point correctly
const {
Editor,
Modifier,
EditorState,
RichUtils,
CompositeDecorator,
EditorChangeType,
getDefaultKeyBinding,
} = Draft;
class Example extends React.Component {
constructor(props){
super(props)
const compositeDecorator = new CompositeDecorator([
{ strategy: getEntityStrategy('LINK'), component: LinkComponent },
]);
this.state = { editorState: EditorState.createEmpty(compositeDecorator) };
this.onChange = (editorState) => { this.setState({editorState}) };
this.handleReturn = this.handleReturn.bind(this);
}
handleReturn(e, editorState) {
e.preventDefault();
const { start, end, text } = getFullWordWithCoordinates(editorState);
const contentState = editorState.getCurrentContent();
const selectionState = editorState.getSelection();
const contentStateWithEntity = contentState.createEntity(
'LINK',
'MUTABLE',
{ status: 'complete' }
);
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newContentState = Modifier.replaceText(contentState,
selectionState.merge({ anchorOffset: start, focusOffset: end }),
`:${text}:`,
null,
entityKey);
const newEditorState = EditorState.set(editorState, { currentContent: newContentState });
this.setState({ editorState: EditorState.moveFocusToEnd(newEditorState) });
return 'handled';
}
render(){
return (
<div style={{border: '1px solid black', padding: '8px'}}>
<Editor
handleReturn={this.handleReturn}
editorState={this.state.editorState}
onChange={this.onChange} />
</div>
)
}
}
function getFullWordWithCoordinates(editorState) {
const selectionState = editorState.getSelection();
const anchorKey = selectionState.getAnchorKey();
const currentContent = editorState.getCurrentContent();
const currentContentBlock = currentContent.getBlockForKey(anchorKey);
const start = selectionState.getStartOffset();
const end = selectionState.getEndOffset();
const blockText = currentContentBlock.getText();
let wholeWordStart = start;
let wholeWordEnd = end;
while (blockText.charAt(wholeWordStart - 1) !== ' ' && wholeWordStart > 0) {
wholeWordStart--;
}
while (blockText.charAt(wholeWordEnd) !== ' ' && wholeWordEnd < blockText.length) {
wholeWordEnd++;
}
return {
text: currentContentBlock.getText().slice(wholeWordStart, wholeWordEnd),
start: wholeWordStart,
end: wholeWordEnd,
};
}
function getEntityStrategy(type) {
return function(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
if (entityKey === null) {
return false;
}
return contentState.getEntity(entityKey).getType() === type;
},
callback
);
};
}
const LinkComponent = (props) => (<span style={{ background: 'red'}}>{props.children}</span>)
ReactDOM.render(
<Example />,
document.getElementById('target')
);