facebookarchive / draft-js

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

How to style custom components html elements ? #2529

Open dzheey opened 4 years ago

dzheey commented 4 years ago

Hello. Thank you so much for your work.

Please help me figure it out. I want to style the elements on both the output and the input.

How do I style unstyled elements and my custom components? For example, I have a custom_btn entity, and a button. When you click on which the word is stylized. After the structure looks like this

const data = <p class ="article_paragraph"> <div class ="custom_btn"> 123 </div></p>;

But when I give this structure to draftjs (simulate data loading), for some reason I get const data=<p class="article_paragraph">123 </p>;

Here's another example with bold text. I expect draftjs to get the structure

<p class = "article_paragraph"> <span class = "custom_bold"> 123 </span> </p> will make the text bold.

But in fact, draftjs trims to <p class = "article_paragraph"> 123 </p> For clarity, I created an example on codesanbox

I spent a lot of time, but I couldn't figure it out. Help me please.

Editor file

//The problem is here. I expect to see the result as when clicking on CUSTOM BTN OR BOLD BTN
const data = `<p class="article_paragraph"><span class="custom_bold">SOME BOLD TEXT</span></p>
<p class="article_paragraph"><div class="custom_btn">AND CUSTOM BTN&nbsp;</div></p>`;
// but i see this
/*<p class="article_paragraph">SOME BOLD TEXT</p>
<p class="article_paragraph">AND CUSTOM BTN&nbsp;</p> */

export default function CustomEditor() {
  const [editorState, setEditorState] = useState(
    EditorState.createWithContent(stateFromHTML(data), decorator)
  );
  return (
    <div className="CustomEditor">
      <Tollbar editorState={editorState} onChange={setEditorState} />
      <Editor
        editorState={editorState}
        onChange={editorState => setEditorState(editorState)}
      />

      <textarea
        disabled
        style={{ width: 800, height: 300, marginTop: 20 }}
        value={stateToHTML(editorState.getCurrentContent(), getOptions())}
      />
    </div>
  );
}

Toolbar

export default function Tollbar({ onChange, editorState }) {
  const [isBold, setIsBold] = useState(false);
  const [isItalic, setIsItalic] = useState(false);

  const toggleInlineStyle = style => {
    if (style === "BOLD") setIsBold(!isBold);
    if (style === "ITALIC") setIsItalic(!isItalic);
    onChange(RichUtils.toggleInlineStyle(editorState, style));
  };

  const setLink = () => {
    // getURL
    const urlValue = prompt("Enter url", "");

    // get contentState
    const contentState = editorState.getCurrentContent();
    // create Entity
    const contentStateWithEntity = contentState.createEntity(
      "LINK",
      "MUTABLE",
      { url: urlValue }
    );
    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
    // оupdate currentContent  editorState
    const newEditorState = EditorState.set(editorState, {
      currentContent: contentStateWithEntity
    });

    onChange(
      RichUtils.toggleLink(
        newEditorState,
        newEditorState.getSelection(),
        entityKey
      )
    );
  };

  const setCustomBtn = () => {

    const contentState = editorState.getCurrentContent();

    const contentStateWithEntity = contentState.createEntity(
      "CUSTOM_BTN",
      "MUTABLE",
      { val1: Math.random() * 10, val2: Math.random() * 20 }
    );
    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();

    const newEditorState = EditorState.set(editorState, {
      currentContent: contentStateWithEntity
    });

    const contentStateWithLink = Modifier.applyEntity(
      contentStateWithEntity,
      newEditorState.getSelection(),
      entityKey
    );

    onChange(EditorState.push(editorState, contentStateWithLink));
  };

return (
    <div className="Tollbar">
      <span
        className={`toolbar-btn ${isBold ? "active" : ""} `}
        onClick={() => toggleInlineStyle("BOLD")}
      >
        B
      </span>
      <span
        className={`toolbar-btn ${isItalic ? "active" : ""} `}
        onClick={() => toggleInlineStyle("ITALIC")}
      >
        I
      </span>
      <span className={`toolbar-btn  `} onClick={() => setLink("LINK")}>
        LINK
      </span>

      <span className={`toolbar-btn  `} onClick={() => setCustomBtn("LINK")}>
        CUSTOM BTN
      </span>
    </div>
  );
}

Decorators

  contentBlock.findEntityRanges(character => {
    const entityKey = character.getEntity();
    return (
      entityKey !== null &&
      contentState.getEntity(entityKey).getType() === "LINK"
    );
  }, callback);
}

const Link = props => {
  const { url } = props.contentState.getEntity(props.entityKey).getData();
  return (
    <a href={url} title={url} className="custom_link">
      {props.children}
    </a>
  );
};

// for CUSTOMbtn
function findCustomBtn(contentBlock, callback, contentState) {
  contentBlock.findEntityRanges(character => {
    const entityKey = character.getEntity();
    return (
      entityKey !== null &&
      contentState.getEntity(entityKey).getType() === "CUSTOM_BTN"
    );
  }, callback);
}

const CustomBtn = props => {
  return <div className="custom_btn">{props.children}</div>;
};

export const decorator = new CompositeDecorator([
  {
    strategy: findLinkEntities,
    component: Link
  },
  {
    strategy: findCustomBtn,
    component: CustomBtn
  }
]);
BiancaArtola commented 3 years ago

Do you find a solution for this?