jpuri / react-draft-wysiwyg

A Wysiwyg editor build on top of ReactJS and DraftJS. https://jpuri.github.io/react-draft-wysiwyg
MIT License
6.42k stars 1.16k forks source link

Unknown DraftEntity key: null. #609

Open dinfyru opened 6 years ago

dinfyru commented 6 years ago

Error: image

Code to run:

        const contentBlock = htmlToDraft(sanitizeHtml(this.wysiwygHtml.value, sanitizeRules));
        const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
        const newEditorState = EditorState.createWithContent(contentState);
        this.onEditorStateChange(newEditorState);

Code to paste:

<a href="https://test.com" target="_blank" data-saferedirecturl="https://www.test.com/url?hl=ru&amp;q=https://test.com&amp;source=gmail&amp;ust=1518687009434000&amp;usg=AFQjCNGJCuM_hG6wsHlvqVTpepzndFgVkQ"><img src="https://proxy/28oL4gKFefkX8wOuCE5zIS5UPzeCfGKO-3mjWWCNk_HZOIIhR9Kxyx9sKG2nNKU_SWOVY_dk_lVVJKxv-P7pRe__6VurG3wi6kOwiHXAERe7RjM=s0-d-e1-ft#https://test.com/images/signatures/new/ira.jpg" alt="" class="CToWUd"></a>

Versions:

    "react-draft-wysiwyg": "^1.12.7",
    "draft-js": "^0.10.5",
    "draftjs-to-html": "^0.8.2",
    "html-to-draftjs": "^1.1.2"
jpuri commented 6 years ago

Hey @Dinfyru : can you plz detail when exactly you get this error ?

dinfyru commented 6 years ago

@jpuri Error, when i running this code

        const contentBlock = htmlToDraft(sanitizeHtml(this.wysiwygHtml.value, sanitizeRules));
        const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
        const newEditorState = EditorState.createWithContent(contentState);
        this.onEditorStateChange(newEditorState);
jpuri commented 6 years ago

draftjs-to-html works for only the HTML generated by wysiwyg itself.

dinfyru commented 6 years ago

@jpuri there is no func draftjs-to-html in this code

        const contentBlock = htmlToDraft(sanitizeHtml(this.wysiwygHtml.value, sanitizeRules));
        const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
        const newEditorState = EditorState.createWithContent(contentState);
        this.onEditorStateChange(newEditorState);
jpuri commented 6 years ago

Ah sorry I mean html-to-draftjs.

dinfyru commented 6 years ago

@jpuri Is there ways to use other html?

jpuri commented 6 years ago

This may help you: https://github.com/sstur/draft-js-utils/tree/master/packages/draft-js-import-html

andershagebakken commented 6 years ago

Hello. I am experiencing the same issue inserting html created with the editor. I use the same code as Finfyru:

const contentBlock = htmlToDraft(html); const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks); const editorState = EditorState.createWithContent(contentState);

jpuri commented 6 years ago

Plz note that html-to-draftjs can take care of only html generated by editor itself.

marcaaron commented 6 years ago

Is there a solution to this yet?

The app I'm building saves exported html to a database and allows users to then come back at a later date and import/edit the content with the editor. But does not work if images are added. Is there some workaround for this? Maybe stringify the data, save it to the database, and recreate the editorState with images? I tried with editorState... but this does not work. Any ideas?

marcaaron commented 6 years ago

Strangely enough... the expected behavior actually works OK sometimes, but other times it causes this error to appear.

jpuri commented 6 years ago

@marcaaron, can you plz share exactly when you get error. You can save editor content as JSON check example 2. Uncontrolled editor component with conversion of content from and to JSON (RawDraftContentState) Here: https://jpuri.github.io/react-draft-wysiwyg/#/demo

In fact that is the recommended way to save editor content.

marcaaron commented 6 years ago

Very nice thanks :)

namnh06 commented 6 years ago

I solved this error. I do a function which one add an image into editor, it's mean I have to get the link of image then add it to img tag of HTML, problem is here. Add it into <p><img src="your_image_link" ></img></p> before parse it into editor again. The p tag is required in editor.

Terryand commented 6 years ago

I have the same problem. When I using tag and delete it, sometimes it will be deleted in views, but actually it not be deleted in data. And then, if I ender Editor again, I get this ERROR.

wanlerong commented 5 years ago

For Me, The reason for the problem is the type was "atomic" but entityRanges is empty

 {
      "key": "as6q7",
      "text": "",
      "type": "atomic",
      "depth": 0,
      "inlineStyleRanges": [],
      "entityRanges": [],
      "data": {}
    }
paradoxxxzero commented 5 years ago

This is a very real problem and I got a good reproduction case:

  1. Go to https://jpuri.github.io/react-draft-wysiwyg/#/demo
  2. Insert an image in the first editor
  3. Go up to place the caret above the image and press delete to remove the very first <p>
  4. You should have:
    <img src="image.jpg" alt="undefined" style="float:none;height: auto;width: auto"/>
    <p></p>

    in the html preview.

First Problem: The image is still in the generated html but not in the editor anymore.

Second problem: If you initialize an editor with this HTML (which is generated by draftjs-to-html) you get the above crash: Invariant Violation: Unknown DraftEntity key: null.

--

Edit: Here's the broken state json dump:

> JSON.stringify(editorState.getCurrentContent().toJSON(), null, 2)

{
  "entityMap": {},
  "blockMap": {
    "c9cv2": {
      "key": "c9cv2",
      "type": "atomic",
      "text": "",
      "characterList": [],
      "depth": 0,
      "data": {}
    },
    "2gj8i": {
      "key": "2gj8i",
      "type": "unstyled",
      "text": " ",
      "characterList": [
        {
          "style": [],
          "entity": "1"
        }
      ],
      "depth": 0,
      "data": {}
    }
  },
  "selectionBefore": {
    "anchorKey": "c9cv2",
    "anchorOffset": 0,
    "focusKey": "c9cv2",
    "focusOffset": 0,
    "isBackward": false,
    "hasFocus": false
  },
  "selectionAfter": {
    "anchorKey": "c9cv2",
    "anchorOffset": 0,
    "focusKey": "c9cv2",
    "focusOffset": 0,
    "isBackward": false,
    "hasFocus": false
  }
}

The first block (c9cv2) crash because block.getEntityAt(0) is null here: https://github.com/jpuri/react-draft-wysiwyg/blob/f0da14fe8d082ee078da1c2eceac3789d400db26/src/renderer/index.js#L11

marchaos commented 5 years ago

Getting this error too - any chance we can look at fixing this?

machnicki commented 5 years ago

It may be problem of DraftJS itself? I got similar problem on Firefox since I have update DraftJS >= 0.10.1. On Chrome it works ok

jakeleventhal commented 5 years ago

Getting this error as well, any idea when this will be fixed on a fix?

momotofu commented 5 years ago

This bug happened when the incoming HTML was missing a prefixed <p></p> tag. When prepending a <p></p> to the incoming html it fixed the error. This is just a temporary fix, but might provide clues to the larger problem. Sometimes when trying to delete an image, the <p></p> tags are deleted, but the editor shows that the image has been deleted.

[missing p tags here. Shows that image is gone in editor preview]
<img src="http://somerandomurl.com" alt="asdf" style="float:none;height: auto;width: auto"/>
<p></p>

If this state gets saved, then it'll error out when you reload.

ViniciusGularte commented 5 years ago

Any fix release to this?

machnicki commented 5 years ago

I have fixed similar issue on Firefox by updating draft js "draft-js": "0.11.0-beta2"

and set global config:

window.__DRAFT_GKX = {
     draft_killswitch_allow_nontextnodes: true,
}
ViniciusGularte commented 5 years ago

@machnicki thank you, it worked here

Hereward commented 5 years ago

Has there been any progress on this bug? I have tried all suggested solutions and nothing works. My last hope is to try prepending <p></p> to the saved html if there is an image at the beginning. It seems like a horrible hack but that is the only option I have left to try.

zhayes commented 5 years ago

This may help you: https://github.com/sstur/draft-js-utils/tree/master/packages/draft-js-import-html

the package can't show image tag !!!!@jpuri

ViniciusGularte commented 5 years ago

My final solution is replace this package for the React Quill package, much better for working with html tags

sanyuelanv commented 5 years ago

i find the error in this: onContentStateChange

in './Editor/index.js you can see the code onContentStateChange(convertToRaw(editorState.getCurrentContent()));

i try to change it to be this code onContentStateChange(editorState);

the error never show

swham-ai commented 5 years ago

whenever I paste an image in a blank editor, I get an error. please fix

ghost commented 5 years ago

This could be because some text uses the figure tag, but draft reserves this tag. You could bypass the "handlePastedText" with your own function like so:

Just pass the parameter handlePastedText={HandlePastedText} in the Editor and import the function below as HandlePastedText.

/**
 *
 * @param text text on clipboard
 * @param html html on clipboard (no IE11 support)
 * @param editorState
 * @param onChange
 * @returns {boolean} true states to editor paste is handled, false will continue with standard paste behavior of editor
 */
const handlePastedText = (text, html, editorState, onChange) => {
    const selectedBlock = getSelectedBlock(editorState);
    if (selectedBlock && selectedBlock.type === 'code') {
        const contentState = Modifier.replaceText(
                editorState.getCurrentContent(),
                editorState.getSelection(),
                text,
                editorState.getCurrentInlineStyle()
        );
        onChange(EditorState.push(editorState, contentState, 'insert-fragment'));
        return true;
    } else if (html) {
        //Figure can be under the copied html, but is reserved in the editor. Since figure contains an img tag, simply strip figure.
        if (html.indexOf('<figure') != -1) {
            html = html.replace(/(<\/?)figure((?:\s+.*?)?>)/g, '');
        }
        const contentBlock = htmlToDraft(html);
        let contentState = editorState.getCurrentContent();
        contentBlock.entityMap.forEach((value, key) => {
            contentState = contentState.mergeEntityData(key, value);
        });
        contentState = Modifier.replaceWithFragment(
                contentState,
                editorState.getSelection(),
                new List(contentBlock.contentBlocks)
        );
        onChange(EditorState.push(editorState, contentState, 'insert-fragment'));
        return true;
    }
    return false;
};
export default handlePastedText;
lstrive-yn commented 4 years ago

after I post an image in editor, I try to enter somthing and got this error;

I resolved this problem by changing the version of draft-js to 0.10.*

bingz1 commented 4 years ago

after I post an image in editor, I try to enter somthing and got this error;

I resolved this problem by changing the version of draft-js to 0.10.*

thanks, this works for me

classicalrehan commented 4 years ago

Also, make sure if you going and pulling prefetch data then it must surround by p tag or something. I don't know exactly but for my case, it's working

editorState: EditorState.createWithContent(
                  ContentState.createFromBlockArray(
                    htmlToDraft("<p>"+MYVARIABLE+"</p>")
                  )
                ),

or editorState: EditorState.createWithContent( ContentState.createFromBlockArray( htmlToDraft("<p>Initial content</p>") ) ),

Liqiankun commented 4 years ago

Any fix for this one? I have the same issue!!!

Liqiankun commented 4 years ago

1029 I guess they are same issues.

ariel-frischer commented 3 years ago

This could be because some text uses the figure tag, but draft reserves this tag. You could bypass the "handlePastedText" with your own function like so:

Just pass the parameter handlePastedText={HandlePastedText} in the Editor and import the function below as HandlePastedText.

/**
 *
 * @param text text on clipboard
 * @param html html on clipboard (no IE11 support)
 * @param editorState
 * @param onChange
 * @returns {boolean} true states to editor paste is handled, false will continue with standard paste behavior of editor
 */
const handlePastedText = (text, html, editorState, onChange) => {
  const selectedBlock = getSelectedBlock(editorState);
  if (selectedBlock && selectedBlock.type === 'code') {
      const contentState = Modifier.replaceText(
              editorState.getCurrentContent(),
              editorState.getSelection(),
              text,
              editorState.getCurrentInlineStyle()
      );
      onChange(EditorState.push(editorState, contentState, 'insert-fragment'));
      return true;
  } else if (html) {
      //Figure can be under the copied html, but is reserved in the editor. Since figure contains an img tag, simply strip figure.
      if (html.indexOf('<figure') != -1) {
          html = html.replace(/(<\/?)figure((?:\s+.*?)?>)/g, '');
      }
      const contentBlock = htmlToDraft(html);
      let contentState = editorState.getCurrentContent();
      contentBlock.entityMap.forEach((value, key) => {
          contentState = contentState.mergeEntityData(key, value);
      });
      contentState = Modifier.replaceWithFragment(
              contentState,
              editorState.getSelection(),
              new List(contentBlock.contentBlocks)
      );
      onChange(EditorState.push(editorState, contentState, 'insert-fragment'));
      return true;
  }
  return false;
};
export default handlePastedText;

@ghost I'm trying to use this, but what is new List() ? is that a function or class I need to import from somewhere? Also I found getSelectedBlock() function from some obscure medium article on draft-js, I'm unsure if it is the correct one but I've got:

 const getSelectedBlock = (editorState:EditorState) => {
    const selection = editorState.getSelection();
    const contentState = editorState.getCurrentContent();
    const blockStartKey = selection.getStartKey();

    return contentState.getBlockMap().get(blockStartKey);
  }
ariel-frischer commented 3 years ago

after I post an image in editor, I try to enter somthing and got this error;

I resolved this problem by changing the version of draft-js to 0.10.*

Unfortunately this did not work for me.

ariel-frischer commented 3 years ago

Ok this code seems to work, I added a couple packages you can see from imports:

import {EditorState, convertToRaw, Modifier} from "draft-js";
import { stateFromHTML } from "draft-js-import-html";
import sanitizeHtml from 'sanitize-html';

const getSelectedBlock = (editorState:EditorState) => {
    const selection = editorState.getSelection();
    const contentState = editorState.getCurrentContent();
    const blockStartKey = selection.getStartKey();

    return contentState.getBlockMap().get(blockStartKey);
  }

  const handlePastedText = (
    text: string,
    html: string,
    editorState: EditorState,
    onChange: (editorState: EditorState) => void
  ) => {
    try {

    const selectedBlock = getSelectedBlock(editorState);
    if (selectedBlock && selectedBlock.getType() === "code") {
      const contentState = Modifier.replaceText(
        editorState.getCurrentContent(),
        editorState.getSelection(),
        text,
        editorState.getCurrentInlineStyle()
      );
      onChange(
        EditorState.push(editorState, contentState, "insert-characters")
      );
      return true;
    } else if (html) {
      // const fixedHTML = html.replace(/<img.*>/gi, "\n");
      let fixedHTML = sanitizeHtml(html)
        .replace(/(<\/?)img((?:\s+.*?)?>)/g, "")
        .replace(/(<\/?)figure((?:\s+.*?)?>)/g, "");
      const blockMap = stateFromHTML(fixedHTML).getBlockMap();
      const newState = Modifier.replaceWithFragment(
        editorState.getCurrentContent(),
        editorState.getSelection(),
        blockMap
      );
      onChange(EditorState.push(editorState, newState, "insert-fragment"));
      return true;
    }
    return false;
 }catch(error) {
      console.error(error);
      return false;
    }
  };
s-kris commented 3 years ago

I got this error, when fontSize is set via editorStyle. I used editorClassName instead.

S4Mi commented 3 years ago

I solved the issue using convertFromHTML from draft-js. My problem was figure HTML tag in the content.

const { contentBlocks, entityMap } = convertFromHTML(this.getInputValue() || '')
const contentState = ContentState.createFromBlockArray(contentBlocks, entityMap)
.
.
.
<Editor {...otherProps} editorState={EditorState.createWithContent(contentState)} />
JoelFernando209 commented 3 years ago

My solution was also deleting the figure tag: htmlValue.replace(/(<\/?)figure((?:\s+.*?)?>)/g, '')

The whole code in case is useful for anyone:

const { contentBlocks, entityMap } = htmlToDraft(
  htmlValue.replace(/(<\/?)figure((?:\s+.*?)?>)/g, '') // Delete <figure> tag that causes DraftJS error
);
const contentState = ContentState.createFromBlockArray(
  contentBlocks,
  entityMap
);
setEditorState(EditorState.createWithContent(contentState));
S4Mi commented 3 years ago

I switched to tinymce self-hosted, at last.

matveeva-as commented 3 years ago

I has same problem with inserting Image from clipboard. And I used this code for updating editorState return AtomicBlockUtils.insertAtomicBlock( newEditorState, entityKey, '' );
I tried to solve this problem, and change '' for ' ' (with space) helped me :/ it's sound crazy, but it works for me)

gotplitz commented 2 years ago

I solved the issue using convertFromHTML from draft-js. My problem was figure HTML tag in the content.

const { contentBlocks, entityMap } = convertFromHTML(this.getInputValue() || '')
const contentState = ContentState.createFromBlockArray(contentBlocks, entityMap)
.
.
.
<Editor {...otherProps} editorState={EditorState.createWithContent(contentState)} />

Hey S4Mi, I had to import a bunch of WordPress posts from an old website from one of my clients, and your solution was the perfect one, using convertFromHTML solved the issue, I think it was because the posts have images in the content.

I just wanted to say thank you!

wk14 commented 2 years ago

I had this error too. I had this.props.text consists of draft-js content Turn out that the EntityRanges was saved as a string(@{offset=0, length=1,key=0}) instead of Object form in mongoDB for unknown reason, so I had to check if there's invalid string in this.props.text.blocks[i].EntityRanges[j] and convert it back to object form before passing into editorState= EditorState.createWithContent(convertFromRaw(this.props.text));

MaxKoldun commented 2 years ago

This is a very real problem and I got a good reproduction case:

  1. Go to https://jpuri.github.io/react-draft-wysiwyg/#/demo
  2. Insert an image in the first editor
  3. Go up to place the caret above the image and press delete to remove the very first

  4. You should have:
<img src="image.jpg" alt="undefined" style="float:none;height: auto;width: auto"/>
<p></p>

in the html preview.

First Problem: The image is still in the generated html but not in the editor anymore.

Second problem: If you initialize an editor with this HTML (which is generated by draftjs-to-html) you get the above crash: Invariant Violation: Unknown DraftEntity key: null.

--

Edit: Here's the broken state json dump:

> JSON.stringify(editorState.getCurrentContent().toJSON(), null, 2)

{
  "entityMap": {},
  "blockMap": {
    "c9cv2": {
      "key": "c9cv2",
      "type": "atomic",
      "text": "",
      "characterList": [],
      "depth": 0,
      "data": {}
    },
    "2gj8i": {
      "key": "2gj8i",
      "type": "unstyled",
      "text": " ",
      "characterList": [
        {
          "style": [],
          "entity": "1"
        }
      ],
      "depth": 0,
      "data": {}
    }
  },
  "selectionBefore": {
    "anchorKey": "c9cv2",
    "anchorOffset": 0,
    "focusKey": "c9cv2",
    "focusOffset": 0,
    "isBackward": false,
    "hasFocus": false
  },
  "selectionAfter": {
    "anchorKey": "c9cv2",
    "anchorOffset": 0,
    "focusKey": "c9cv2",
    "focusOffset": 0,
    "isBackward": false,
    "hasFocus": false
  }
}

The first block (c9cv2) crash because block.getEntityAt(0) is null here: 

https://github.com/jpuri/react-draft-wysiwyg/blob/f0da14fe8d082ee078da1c2eceac3789d400db26/src/renderer/index.js#L11

I have exactly the same problem and I did not find any better solution than adding <p></p> through regex if html starts with img tag.

thorgan376 commented 4 months ago

I Followed this solution and it works really well for me: https://github.com/jpuri/react-draft-wysiwyg/issues/979#issuecomment-672142998

In my case, i'm using this on Nextjs with typerscript and also want an functional Component so I have this:

.tsx Typerscript -- I turned it into my shared component myBlockRenderer with param is the content Block --

import { MediaComponentProps } from "@/types/file-upload-swagger";
import { ContentBlock } from "draft-js";

// Convert image type to mediaComponent
const MediaComponent: React.FC<MediaComponentProps> = ({ block, contentState, blockProps }) => {
    const { foo } = blockProps;
    const data = contentState.getEntity(block.getEntityAt(0)).getData();
    const emptyHtml = ' ';

return (
      <div className="flex justify-center">
        {emptyHtml}
        <img
          src={data.src}
          alt={data.alt}
          style={{ height: data.height, width: data.width || 'auto' }}
          className="rounded-lg"
        />
      </div>
    );
  };

  const myBlockRenderer = (contentBlock: ContentBlock) => {
    const type = contentBlock.getType();

// Prevent type atomic error from happening by converting image type to mediaComponent
    if (type === 'atomic') {
      return {
        component: MediaComponent,
        editable: false,
        props: {
          foo: 'bar',
        },
      };
    }
  };

  export default myBlockRenderer;

-- Next, i defined type MediaComponentProps in another .ts file --

export type MediaComponentProps = {
  block: any;
  contentState: any;
  blockProps: any;
};

-- If you don't want to put inside another file, you can instead put this directly in the shared component--

type MediaComponentProps = {
  block: any;
  contentState: any;
  blockProps: any;
};

--Last, import the shared component back to the Editor and pass it to the customBlockRenderFunc--

<Editor
                customBlockRenderFunc={myBlockRenderer}
                editorState={editorState}
                wrapperStyle={{
                  border: '1px solid #ccc',
                  padding: '5px',
                  borderRadius: '5px',
                }}
                toolbarStyle={{
                  border: '0px solid #ccc',
                  borderBottom: '1px solid #ccc',
                }}
                toolbar={{
                  options: [
                    'inline',
                    'blockType',
                    'fontSize',
                    'fontFamily',
                    'list',
                    'textAlign',
                    'link',
                    'embedded',
                    'emoji',
                    'image',
                    'remove',
                    'history',
                  ],
                  inline: {
                    inDropdown: true,
                  },
                  image: {
                    urlEnabled: true,
                    uploadEnabled: true,
                    uploadCallback: uploadImageCallBack,
                    alt: { present: true, mandatory: true },
                  },
                }}
                onEditorStateChange={setEditorState}
              />
            )}

Enjoy