Draft-js: How to add space before/after link?

Created on 4 Nov 2016  路  2Comments  路  Source: facebook/draft-js

I searched for answer to this question on many sites but could not find it one so far, so posting my concern here.

Here is the scenario:
Following the Link example provided on your this

I am using the following code to convert a selected text into hyperlink after when user confirms the hyperlink URL:

`
_confirmLink(urlValue) {
const {editorState} = this.state;
const entityKey = Entity.create('LINK', 'MUTABLE', {url: urlValue});

this.setState({
  showURLInput: false,
  editorState: RichUtils.toggleLink(
    editorState,
    editorState.getSelection(),
    entityKey
  )
}, () => 
  setTimeout(() => this.refs.editor.focus(), 100);
});

}
`

Now suppose user has typed text abc and then he supplies url for it in the prompt e.g http://yahoo.com
Text abc is converted to hyperlink, fine cool.

But after that cursor in text editor slips immediately to the start of line. When user manually tries to move that cursor to the end of line and types again something, text editor shows that typed text at start of line which is quite weird.

In my opinion a space character should be inserted before and after the hyperlink, so that user is able to type something after that. Also cursor must stay at end of hyperlink rather than at start of line.

How can I achieve that? Thanks in advance.

Most helpful comment

@gdehmlow Using your idea and with some little hook and crook, it worked for me. Here is my code:

_confirmLink(urlValue) {

    const {editorState} = this.state;

    const entityKey = Entity.create(
      'LINK',
      'MUTABLE',
      {url: urlValue}
    );

    let selection = editorState.getSelection();

    const contentState = Modifier.applyEntity(
      editorState.getCurrentContent(),
      selection,
      entityKey
    );

    let linked = EditorState.push(
      editorState,
      contentState,
      'apply-entity'
    );

    let collapsed = selection.merge({
                        anchorOffset: selection.getEndOffset(), 
                        focusOffset: selection.getEndOffset()
                      });

    let newEditorState = EditorState.forceSelection(linked, collapsed);

    this.setState({
      showURLInput: false,
      editorState: newEditorState
    }, () => {
      setTimeout(() => {

        this.refs.editor.focus();

        const {editorState} = this.state;
        let selection = editorState.getSelection();

        let cs = Modifier.insertText(
          editorState.getCurrentContent(),
          selection,
          ' '
        );

        const newEditorState = EditorState.push(
          editorState,
          cs,
          'insert-text'
        );

        this.setState({editorState: newEditorState});
       }, 10);
    });
  }

All 2 comments

I force the selection to the end of the Link entity after I insert it. Here's code I have that achieves this:

export function createLink(editorState, url) {
  if (!url) { return editorState; }

  const entityKey = Entity.create(
    ENTITY_TYPE_LINK,
    'MUTABLE',
    { url }
  );

  let selection = editorState.getSelection();
  const contentState = Modifier.applyEntity(
    editorState.getCurrentContent(),
    selection,
    entityKey
  );

  const linked = EditorState.push(
    editorState,
    contentState,
    'apply-entity'
  );

  let collapsed = selection.merge({
    anchorOffset: selection.getEndOffset(), focusOffset: selection.getEndOffset(),
  });


  return EditorState.forceSelection(linked, collapsed);
}

Note that I've already guaranteed previously that before this function that my anchor and focus are in the same block. You may want to do that before doing the collapsing or even inserting the Link.

Then, when I refocus the editor after setting state, my selection is at the end of the link.

For the space after the hyperlink, here's what I have in my editor component:

  handleBeforeInput(chars) {
    let editorState = this.getEditorState();

    if (chars === ' ' && isEntitySelected(editorState, ENTITY_TYPE_LINK)) {
      editorState = insertText(editorState, ' ');
      this.onChange(editorState);
      return true;
    }
    return false;
  },

I pass this as the handleBeforeInput prop to the draft editor.

This actually isn't perfect since it splits your link up into two if you insert a space in the middle - what you should do is check that the selection is at the _end_ of the link entity (which is a simple check: get the current block key of your end offset with editorState.getSelection().getEndKey(), then let block = editorState.getCurrentContent().getBlockForKey(key), then something like

let endKey = block.getEntityAt(endOffset - 1);
let endEntityType = endKey && Entity.get(endKey).getType()
let afterEndKey = block.getEntityAt(endOffset);
let afterEndEntityType = afterEndKey && Entity.get(afterEndKey).getType();
if (chars === ' ' && endEntityType === ENTITY_TYPE_LINK && afterEndEntityType !== ENTITY_TYPE_LINK)

@gdehmlow Using your idea and with some little hook and crook, it worked for me. Here is my code:

_confirmLink(urlValue) {

    const {editorState} = this.state;

    const entityKey = Entity.create(
      'LINK',
      'MUTABLE',
      {url: urlValue}
    );

    let selection = editorState.getSelection();

    const contentState = Modifier.applyEntity(
      editorState.getCurrentContent(),
      selection,
      entityKey
    );

    let linked = EditorState.push(
      editorState,
      contentState,
      'apply-entity'
    );

    let collapsed = selection.merge({
                        anchorOffset: selection.getEndOffset(), 
                        focusOffset: selection.getEndOffset()
                      });

    let newEditorState = EditorState.forceSelection(linked, collapsed);

    this.setState({
      showURLInput: false,
      editorState: newEditorState
    }, () => {
      setTimeout(() => {

        this.refs.editor.focus();

        const {editorState} = this.state;
        let selection = editorState.getSelection();

        let cs = Modifier.insertText(
          editorState.getCurrentContent(),
          selection,
          ' '
        );

        const newEditorState = EditorState.push(
          editorState,
          cs,
          'insert-text'
        );

        this.setState({editorState: newEditorState});
       }, 10);
    });
  }

Was this page helpful?
0 / 5 - 0 ratings