question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Cursor positioning problem using Entity

See original GitHub issue

Hello, it’s my first post here, so sorry in advance for any mistake.

I’m using Draft to create a message input component.

I’m using decorators to decorate “hashtags”, and Entity to render emojis.

I’m using entities for emojis in order to set the text “IMMUTABLE”, and be able to delete the whole text from the emoji at once, which is a pattern like this :emoji:

The problem is with the input focus when rendering the emojis. When the cursor is “in the front” of the emoji, it starts typing “behind” the emoji.

I’ve found that setting data-offset-key property into my emoji component improved a lot the control of the cursor,

DOM using data-offset-key: http://hnordt.d.pr/1ghat+

Problem with the cursor: http://hnordt.d.pr/1bk5d+

As you can see, when the cursor is in the next character after the emoji (Entity), it writes before the emoji. Only when navigating to the next character, it begins typing in the correct span.

Code when adding an emoji (emoji.colons is the emoji text ie “:emoji:”):

 handleSelectEmoji(emoji) {
    const { editorState } = this.state
    const currentContent = editorState.getCurrentContent()
    const selection = editorState.getSelection()

    // Adding emoji and applying entity to it
    const entityKey = Entity.create('EMOJI', 'IMMUTABLE', {emoji: emoji.id})
    let newContent = Modifier.insertText(currentContent, selection, emoji.colons)
    let newState = EditorState.createWithContent(newContent, compositeDecorator)
    let selectionState = SelectionState.createEmpty(newContent.getFirstBlock().getKey())
    let focusOffset = selection.getFocusOffset() + emoji.colons.length
    selectionState = selectionState.merge({
      anchorOffset: selection.getAnchorOffset(),
      focusKey: newContent.getFirstBlock().getKey(),
      focusOffset: focusOffset,
    })
    newContent = Modifier.applyEntity(newContent, selectionState, entityKey)
    newState = EditorState.createWithContent(newContent, compositeDecorator)

    //Setting cursor position after inserting emoji to content
    selectionState = selectionState.merge({
      anchorOffset: focusOffset,
      focusKey: newContent.getFirstBlock().getKey(),
      focusOffset: focusOffset,
    })
    newState = EditorState.forceSelection(newState, selectionState)

    this.setState({ editorState: newState })
    setTimeout(() => {
      this.focus()
    },0)
  }

Decorator to transform emoji text in image:

const EmojiSpan = (props) => {
  return (
    <span data-offset-key={props.offsetKey} className="emoji-input-message">
      <Emoji
        sheetURL="img/sheet_apple_64.png"
        emoji={{id:props.decoratedText.substring(1, props.decoratedText.length -1), skin: 1}}
        size={22}
        skin={1}
      />
    </span>
  )
}

function findEmojis(contentBlock, callback) {
  contentBlock.findEntityRanges(
    (character) => {
      const entityKey = character.getEntity();
      return (
        entityKey !== null &&
        Entity.get(entityKey).getType() === 'EMOJI'
      );
    },
    callback
  );
}

const compositeDecorator = new CompositeDecorator([
  {
    strategy: flowStrategy,
    component: FlowSpan,
  },
  {
    strategy: findEmojis,
    component: EmojiSpan
  }
])

Any ideas to solve that? Many thanks!

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:13
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

7reactions
nkemcelscommented, Jan 21, 2020

While waiting for a permanent fix for this issue, Here is what I implemented

let contentState = this.state.editorState.getCurrentContent();
let currentSelection = this.state.editorState.getSelection();

contentState = contentState.createEntity("EMOJI", "IMMUTABLE", emo.colons + "\u200A");
let emoEntityKey = contentState.getLastCreatedEntityKey();

let newContentState = Modifier.insertText(contentState, currentSelection, emo.colons + "\u200A", null, emoEntityKey);
let newEditorState = EditorState.push(this.state.editorState, newContentState, "insert-characters");

newEditorState = EditorState.push(this.state.editorState, newContentState, "insert-characters");
newEditorState = EditorState.moveFocusToEnd(newEditorState);
newEditorState = EditorState.forceSelection(newEditorState, newContentState.getSelectionAfter())
        
this.setState(editorState: newEditorState);

And then, the decorator strategy function:

function emojiMartRenderStrategy(contentBlock, callback, contentState) {
    contentBlock.findEntityRanges(
        character => {
            const entityKey = character.getEntity();
            return entityKey !== null && contentState.getEntity(entityKey).getType() === "EMOJI";
        },
        (start, end) => callback(start, end - 1)
    );
}

notice that I’m calling the callback here with end - 1. The reason for this is because I’m attaching a thin space character (\u+200A) to the entity data when creating the emoji entity. This will make it possible for the cursor to be able to move in front of and behind the emoji. For those who mind, here is how I render the emoji entity (using emoji-mart)

const EmojiMartIcon = props => {
    let emoji = Emoji({
        html: true,
        set: "apple",
        emoji: props.decoratedText,
        size: props.emojiSize || 22
    });
    return emoji ? (
        <span
            style={{ width: 24, display: "inline-block" }}
            dangerouslySetInnerHTML={{
                __html: emoji
            }}
        />
    ) : (
        <>{props.decoratedText || props.emojiColons}</>
    );
};
7reactions
sophiebitscommented, Sep 4, 2016

@stopachka This sounds like the inline-block problem we discussed this week. I don’t think we know of any good solution currently. On Facebook we implement emojis by rendering a wide space and adding the emoji image as a background image.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Cursor Positioning - Rocket Software
Cursor Positioning. The structure editor has to decide where to position the cursor after processing. Some ProcScript statements and structure editor ...
Read more >
aframe get cursor position and set entity on that postion
The only thing wrong was that cursor.components.intersectedEl does not seem to be a thing anymore. Still, at least for my case, that was...
Read more >
EntityCursor (Oracle - Berkeley DB Java Edition API)
When it is opened, a cursor is not initially positioned on any value; in other words, it is uninitialized. Most methods in this...
Read more >
Using SQL Server Cursors with Entity Framework Core
Advanced positioning methods like Absolute and Relative allow you to skip around a defined result set more logically. Disadvantages of MSSQL ...
Read more >
Find an entity under the cursor position using Win32 and ...
By Fenton Webb Issue How do I find the entity under the cursor position using raw Win32? Solution The best way to find...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found