springload / draftail

📝🍸 A configurable rich text editor built with Draft.js
https://www.draftail.org/
MIT License
616 stars 64 forks source link

Add font color picker example #153

Closed favoyang closed 3 years ago

favoyang commented 6 years ago

It would be appreciated if draftail can modify font color for text selection.

thibaudcolas commented 6 years ago

Hey @favoyang, have you tried building this with a custom inline style? I think you could create one that sets color quite easily.

The Draftail examples has a "Redacted" inline style in the "Custom formats" editor that's quite similar – it sets the background-color, not color, but otherwise it's the same: https://www.draftail.org/examples/

favoyang commented 6 years ago

Thanks for the help. It seems combined with a color picker popup, it can be very useful colored text feature. A nice built-in feature to have.

thibaudcolas commented 6 years ago

@favoyang is there a specific reason you think it would work better built-in instead of as an extension?

favoyang commented 6 years ago

Extension works for me as well. I just assume that a large part of audiences of draftail is editor rather than end user. So I prefer give them more feature than control. And text color is quite trivial, you probably won't use it often, but there's always one or two time you'll need it.

thibaudcolas commented 6 years ago

đź‘Ťif you or anyone else wants to work on this I think it would be a good start to add it on the examples page, I'm happy to help where I can.

thibaudcolas commented 6 years ago

As part of Hacktoberfest, I think it would be pretty cool if this could be built as an example of an extension inside examples/.

Could use something like https://github.com/Simonwep/pickr (or another color picker dependency).

Here are related Draft.js projects that already have a color picker:

alvinthen commented 6 years ago

I'm working on this, but stuck on how to commit the editor state. I have managed to show the color picker.

Basically I'm using react-color, in the examples, using the controls attribute. I have received the getEditorState but I don't know how to continue. And due to the structure of the examples, I don't know how to add new inline styles, and then commit the editor state.

Does inlineStyles takes a function? Advice needed to proceed.

thibaudcolas commented 6 years ago

Hey @alvinthen, nice to hear!

The controls API has getEditorState to get the whole editor state, and also onChange to update it. That's most likely what should be used to apply the changes to the editor state.

For example, using the Draft.js RichUtils.toggleInlineStyle():

const { getEditorState, onChange } = this.props;
let state = getEditorState();
state = RichUtils.toggleInlineStyle(state, 'COLOR_FF0000');
onChange(state);

Alternatively this could be built using an entity that has a color attribute in its data. I'm not entirely sure which would be the most appropriate.

alvinthen commented 6 years ago

Hey! Thanks for the reply!

Correct me if I'm wrong, in order to have new inline styles, I would have to add the new styles into inlineStyles of EditorWrapper, right? I'm not quite sure how to pass the new styles from the plugin to example.js without changing too much of the current structure. Some insights would help.

thibaudcolas commented 6 years ago

@alvinthen oops, yes indeed. I just realised inlineStyles relies on the style types (COLOR_FF0000) to be provided upfront. Draftail doesn't provide a way to implement the Draft.js customStyleFn.

I think it would be easier to do this with the entity type API instead of controls – then you can define a COLOR entity.

Could you share what you've done so far so I can help further?

alvinthen commented 6 years ago

This is what I have done using controls, stuck at handling onChange of the color picker.

  handleChange(color) {
    const { getEditorState, onChange } = this.props;
    const editorState = getEditorState();

    let nextEditorState = editorState;

    // What goes here...
    onChange(nextEditorState);
  }

I guess it's as what you said that we can't utilise customStyleFn in Draftail, I'm trying out with the entity API now.

thibaudcolas commented 3 years ago

I’ve finally merged #165, converting @alvinthen’s initial work from using entities to inline styles ( see https://github.com/springload/draftail/pull/165#issuecomment-817197525 for reasoning). I think there’s definitely a place for an entity-based implementation though, so here is the entity-based implementation for reference:

// @flow
import React from "react";
import type { Node } from "react";
import { ContentState } from "draft-js";

export const COLOR_ICON =
  "M322.018 832l57.6-192h264.764l57.6 192h113.632l-191.996-640h-223.236l-192 640h113.636zM475.618 320h72.764l57.6 192h-187.964l57.6-192z";

type Props = {|
  entityKey: string,
  contentState: ContentState,
  children: Node,
|};

const Color = (props: Props) => {
  const { entityKey, contentState, children } = props;
  const { color } = contentState.getEntity(entityKey).getData();
  return <span style={{ color }}>{children}</span>;
};

export default Color;
import React, { Component } from "react";
import { RichUtils, EditorState } from "draft-js";
import type { EntityInstance } from "draft-js";

// $FlowFixMe
import { ChromePicker } from "react-color";

import Modal from "../components/Modal";

type Props = {|
  editorState: EditorState,
  onComplete: (EditorState) => void,
  onClose: () => void,
  entityType: {
    type: string,
  },
  entity: ?EntityInstance,
|};

type State = {|
  color: string,
|};

class ColorSource extends Component<Props, State> {
  constructor(props: Props) {
    super(props);

    const { entity } = this.props;
    const state = {
      color: "#fff",
    };

    if (entity) {
      const data = entity.getData();
      state.color = data.color;
    }

    this.state = state;

    this.onRequestClose = this.onRequestClose.bind(this);
    this.onChangeColor = this.onChangeColor.bind(this);
    this.onConfirm = this.onConfirm.bind(this);
  }

  /* :: onConfirm: () => void; */
  onConfirm() {
    const { color } = this.state;
    const { editorState, entityType, onComplete } = this.props;

    const contentState = editorState.getCurrentContent();
    const data = { color };
    const contentStateWithEntity = contentState.createEntity(
      // Fixed in https://github.com/facebook/draft-js/commit/6ba124cf663b78c41afd6c361a67bd29724fa617, to be released.
      // $FlowFixMe
      entityType.type,
      "MUTABLE",
      data,
    );
    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
    const nextState = RichUtils.toggleLink(
      editorState,
      editorState.getSelection(),
      entityKey,
    );
    onComplete(nextState);
  }

  /* :: onRequestClose: (e: SyntheticEvent<>) => void; */
  onRequestClose(e: SyntheticEvent<>) {
    const { onClose } = this.props;
    e.preventDefault();

    onClose();
  }

  /* :: onChangeColor: (color: {| hex: string |}) => void; */
  onChangeColor(color: {| hex: string |}) {
    this.setState({ color: color.hex });
  }

  render() {
    const { color } = this.state;
    return (
      <Modal
        onRequestClose={this.onRequestClose}
        onAfterOpen={() => {}}
        isOpen
        contentLabel="Pick a color"
      >
        <div className="ColorSource">
          <ChromePicker onChangeComplete={this.onChangeColor} color={color} />
          <button type="button" onClick={this.onConfirm}>
            Save
          </button>
        </div>
      </Modal>
    );
  }
}

export default ColorSource;