Tushar Shahi
Tushar Shahi

Reputation: 20431

DraftJs : Custom button to add custom HTML + CSS

Using DraftJs with WYSWYG: I need to create a custom button in the toolbar. On clicking the button, some custom HTML (which includes <button>) is added to the content itself.

I know how to add a custom button, the docs are there. The button gets added and onclick I call the defined method insertText. But with insertText(a method in the DraftModifier class of draft.js) it only seems to add my HTML code as a string. Is there a different method I should use to add custom HTML into the content. I want to add real HTML.

Here is the TS declaration:

  static insertText(
    contentState: ContentState,
    targetRange: SelectionState,
    text: string,
    inlineStyle?: DraftInlineStyle,
    entityKey?: string
  ): ContentState;

Upvotes: 0

Views: 2156

Answers (2)

diedu
diedu

Reputation: 20775

You can use replaceWithFragment. To build the fragment you want to insert, first convert the string to HTML blocks with the utility convertFromHTML

    const htmlContent = convertFromHTML(text);

then, use another utility to pass the array of content blocks to a map

    const htmlContentMap = BlockMapBuilder.createFromArray(
      htmlContent.contentBlocks
    );

and call replaceWithFragment the same way you were calling insertText, but with the map of HTML content you built

    const newContent = Modifier.replaceWithFragment(
      currentContent,
      currentSelection,
      htmlContentMap
    );

UPDATE

DraftJS doesn't support the button tag by default but you can add it as a custom block render

first, create a map of types by merging the default supported tags with the button tag

import {
...
  DefaultDraftBlockRenderMap,
...
} from "draft-js";

...
const mapWithButton = Map({
  button: {
    element: "button"
  }
}).merge(DefaultDraftBlockRenderMap);
...

and pass it as the third argument to the convertFromHTML function

    const htmlContent = convertFromHTML(
      text,
      undefined,
      mapWithButton
    );

create a custom block renderer function and pass it to the Editor

function myBlockRenderer(contentBlock: ContentBlock) {
  const type = contentBlock.getType();
  if (type === "button") {
    return {
      component: () => {
        return (
          <button onClick={() => console.log("doesn't seem to work :(")}>
            {contentBlock.getText()}
          </button>
        );
      },
      editable: false,
    };
  }
}
...
          <Editor
            ...
            customBlockRenderFunc={myBlockRenderer}
          />

It kind of works because it shows the button, although sometimes when there is text before the point when you insert it, it merges the previous text with the button text without showing the button. The click doesn't work either probably because of this

If your custom block renderer requires mouse interaction, it is often wise to temporarily set your Editor to readOnly={true} during this interaction. In this way, the user does not trigger any selection changes within the editor while interacting with the custom block.

https://codesandbox.io/s/insert-html-draftjs-73rmc?file=/src/Editor.tsx

Upvotes: 2

OfirD
OfirD

Reputation: 10460

In the documentation of react-draft-wysiwyg, there's a dedicated part for using html, cited below. The essential steps are:

  1. Use a library that converts html to Draft.js ContentBlock.
  2. Initialize Draft.js ContentState with that ContentBlock.
  3. Initialize Draft.js ContentEditor with that ContentState
  4. Set react-draft-wysiwyg's Editor's editorState with that ContentEditor object.

Here are two live examples for achieving that: 1, 2.

Citation from the documentation of react-draft-wysiwyg:

draftjs-to-html can be used to convert RawDraftContentState to html.
html-to-draftjs provides the option to convert HTML generated by react-draft-wysiwyg back to draftJS ContentState which can be used to initialize the Editor.

import React, { Component } from 'react';
import { EditorState, convertToRaw, ContentState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
 
class EditorConvertToHTML extends Component {
 constructor(props) {
   super(props);
   const html = '<p>Hey this <strong>editor</strong> rocks 😀</p>';
  const contentBlock = htmlToDraft(html);
  if (contentBlock) {
    const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
    const editorState = EditorState.createWithContent(contentState);
    this.state = {
      editorState,
    };
  }
}

onEditorStateChange: Function = (editorState) => {
  this.setState({
    editorState,
  });
};

render() {
  const { editorState } = this.state;
  return (
    <div>
      <Editor
        editorState={editorState}
         wrapperClassName="demo-wrapper"
        editorClassName="demo-editor"
        onEditorStateChange={this.onEditorStateChange}
      />
      <textarea
        disabled
        value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
      />
    </div>
  );
}
}

Upvotes: 0

Related Questions