facebookarchive / draft-js

A React framework for building text editors.
https://draftjs.org/
MIT License
22.57k stars 2.64k forks source link

how to create a new Entity and insert it at the cursor position #852

Open neove opened 7 years ago

neove commented 7 years ago

When i use Draf.js , I want to create a new Entity and insert it at the cursor pointer , for example : just create a link entity and inset it . I try to use the Modify. applyEntity , but in my case, there is no selectee range , i wanner which API I should user here , thank you for example: when i click the submit button, i will insert some text 'click me for infomation' and it's url is 'www.google.com' 2016-12-08 10 20 39 2016-12-08 10 31 14

mzbac commented 7 years ago

@neove check the examples below: https://github.com/facebook/draft-js/blob/master/examples/draft-0-9-1/link/link.html#L1 https://github.com/facebook/draft-js/blob/master/examples/draft-0-10-0/link/link.html#L1

neove commented 7 years ago

thank u , maybe I didn't make myself clear. the link example here need to have the select range , but in my case , i just want to insert a link at the cursor position rather than operate the select range, i am really confused about this , thank u @mzbac

mzbac commented 7 years ago

@neove in this case, I would just insert a new custom content block and make that custom block to display as inline block..

cruz02148 commented 7 years ago

@neove I'm doing something very similar to you and I was wondering if you got this to work?

rblakejohnson commented 7 years ago

Hey @neove I had this exactly same question starting out with draft js last week :) I searched and searched and couldn't find an example of this thing that I thought might be a basic and common use case for draft js. I came up with a solution after scouring the issues and docs ;) So I'm posting it here as a contained example of clicking a button that creates an Entity at the cursor position. (And hopefully creating some SEO juice) The magic happens in the insertPlaceholder function. Since you want to insert the Entity at the cursor position the selection is collapsed. You want to use Modifier.insertText (see the docs for details). In my case I want an IMMUTABLE Entity that I can style however I want. The CompositeDecorator is needed to provide a function to find PLACEHOLDER Entities and render a Placeholder component.

const React = require('react');
const {Editor, EditorState, convertToRaw, convertFromRaw, Entity, Modifier, CompositeDecorator} = require('draft-js');

const rawContent = {
  blocks: [
    {
      text: 'Hey First name',
      type: 'unstyled',
      entityRanges: [{key: 'first', length: 10, offset: 4}]
    },
    {
      text: '',
      type: 'unstyled',
    },
    {
      text: 'Placeholders can be inserted at the cursor position by clicking that button up there.',
      type: 'unstyled',
    },
  ],
  entityMap: {
    first: {
      type: 'PLACEHOLDER',
      mutability: 'IMMUTABLE',
      data: {
        content: 'firstName', // can be whatever
      },
    },
  },
};

const styles = {
    editor: {
        border: '1px solid gray',
        minHeight: 300,
        cursor: 'text',
    },
    placeholder: {
        display: 'inline-block',
        background: 'lightBlue',
        padding: '0 10px',
        borderRadius: 99
    },
};

const Placeholder = props => (
    <span {...props} style={styles.placeholder}>
        {props.children}
    </span>
);

const decorator = new CompositeDecorator([{
    strategy: findPlaceholders,
    component: Placeholder,
}]);

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

module.exports = React.createClass({
    getInitialState() {
        return {
            editorState: EditorState.createWithContent(convertFromRaw(rawContent), decorator)
        }
    },

    onChange(editorState) {
        this.setState({editorState})
    },

    logState() {
        const content = this.state.editorState.getCurrentContent();
        console.log(convertToRaw(content));
    },

    insertPlaceholder(label, meta) {
        const editorState = this.state.editorState;
        const currentContent = editorState.getCurrentContent();
        const selection = editorState.getSelection();
        const entityKey = Entity.create('PLACEHOLDER', 'IMMUTABLE', {meta});
        const textWithEntity = Modifier.insertText(currentContent, selection, label, null, entityKey);

        this.setState({
            editorState: EditorState.push(editorState, textWithEntity, 'insert-characters')
        }, () => {
            this.focus();
        });
    },

    focus() {
        this.refs.editor.focus();
    },

    render() {
        return (
            <div>
                <button type="button" onClick={this.insertPlaceholder.bind(null, 'Some thing', 'Some thing about some thing')}>
                    Some thing
                </button>
                <div style={styles.editor} onClick={this.focus}>
                    <Editor editorState={this.state.editorState}
                            ref="editor"
                            onChange={this.onChange} />
                </div>
                <button type="button" onClick={this.logState}>Log</button>
            </div>
        );
    }
});

I left some basic functionality I've seen in draft js editor examples like handling focus and logging state and initializing editorState with converted raw content and a decorator. I hope this helps you or someone else out 👍 I'd appreciate suggestions and feedback anyone has for better ways of working with entities and decorators in this way.

lednhatkhanh commented 7 years ago

Does anybody make it works?

openzig commented 6 years ago

There are two ways in my mind:

  1. This will insert mention to current editor, very similar with the answer @rblakejohnson const currentContent = editorState.getCurrentContent(); const selection = editorState.getSelection(); const entityKey = Entity.create('MENTION', 'IMMUTABLE', {"text":'apple',"value":'apple',"url":'apple'}); const textWithEntity = Modifier.insertText(currentContent, selection, '@APPLE', null, entityKey); this.setState({ editorState: EditorState.push(editorState, textWithEntity, 'insert-characters') });

  2. There is one function called addEntityToContentState. But I have not tried it yet https://github.com/facebook/draft-js/blob/master/src/model/transaction/addEntityToContentState.js

David05500 commented 4 years ago

@openzig THANK YOUU, I searched the whole web for your answer!!!!

PierreTurnbull commented 3 years ago

@openzig This works great however it does not work when there is a range selection, instead of just a cursor. Using replaceText instead of insertText fixes the problem

const editorState = this.state.editorState;
const currentContent = editorState.getCurrentContent();
const selection = editorState.getSelection();
const entityKey = Entity.create('PLACEHOLDER', 'IMMUTABLE', {meta});
const textWithEntity = Modifier.replaceText(currentContent, selection, label, null, entityKey);
somewonderfulguy commented 3 years ago

@openzig Hi, I've come into that the previously applied styles to the text will be cleared with such solution (such as, bold / italic / font-size / etc.). Is there any way to keep it?