whomba
whomba

Reputation: 387

Draft JS Modifier.ReplaceText undo/redo error

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

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')
);

Upvotes: 0

Views: 2771

Answers (1)

Jiang YD
Jiang YD

Reputation: 3311

the EditorState.push() API says Based on the changeType, this ContentState may be regarded as a boundary state for undo/redo behavior. I found the user defined changeType will make a boundary, so just make a push:

change

const newEditorState = EditorState.set(editorState, { currentContent: newContentState });

with

const newEditorState = EditorState.push(editorState, newContentState ,"addentity");

Upvotes: 2

Related Questions