Building iWisdom: completing the editor

In a previous post, I wrote about how to make the description rich text for iWisdom. I had already setup an editor using DraftJS and implemented a part of it.

So far, I was able to add text to the editor and make it bold, italic or underline using commands: Cmd+B, Cmd+I and Cmd+U. I was also able to add a hashtag link in the editor. When I add a word starting with the # symbol, it automatically becomes a link.

However, there were various other things remaining in the editor. For example, I needed to add the ability to style text in H1, H2, etc. I also want to add the ability to add list and code blocks. Apart from this, I needed to take the state of an editor into my top-level component so that I can save it. Also, I should be able to pass back the description to the editor and render it accordingly. Apart from this, clicking on hashtag links should open another wisdom. I should also be able to insert these hashtags in the editor from the Lookup component on the side.

Certainly, a lot of functionality was remaining. In this post, I will write about how I achieved all of these.

First, I need to add the ability to style elements. For this I should have buttons at the top of the editor like ‘H1’, ‘H2’, etc. that I would click to style the elements. To create the buttons, create a new component called ‘BlockStyleElements’ in the same file:

class BlockStyleElement extends Component {
   render () {
     return (div className="blockstyleelements">
       {buttons}
       </div>);
   }
}

The buttons in this panel will come from an array which defines the different styles to be applied. Create an array called “BLOCK_TYPES” in this file:

const BLOCK_TYPES = [
  {label: 'H1', style: 'header-one'},
  {label: 'H2', style: 'header-two'},
  {label: 'H3', style: 'header-three'},
  {label: 'H4', style: 'header-four'},
  {label: 'H5', style: 'header-five'},
  {label: 'H6', style: 'header-six'},
  {label: 'Blockquote', style: 'blockquote'},
  {label: 'UL', style: 'unordered-list-item'},
  {label: 'OL', style: 'ordered-list-item'},
  {label: 'Code Block', style: 'code-block'}
];

Now, use this array to render the buttons in ‘BlockStyleElements’ component:

render () {
  var buttons = BLOCK_TYPES.map(type => {
    return <button className="blockStyleButton">{type.label}</button>;
  });
}

Use this element just above the editor like this:

<BlockStyleElements />

Now, I need to make these buttons work. They should apply the style on a text. For that, I need to add a click handler in the BlockStyleElements and pass a function in WisdomEditor through props.

In BlockStyleElements:

<button 
  className="blockStyleButton"
  onClick = {() => this.props.applyBlockStyle(type.style)}
  >{type.label}</button>

Then pass ‘applyBlockStyle’ prop in WisdomEditor:

<BlockStyleElements applyBlockStyle= {(style) => this.applyBlockStyle(style)} />

Now, add this handler in the WisdomEditor:

applyBlockStyle (style) {

}

To apply block style on a text, I will use the RichUtils:

applyBlockStyle (style) {
  var newState = RichUtils.toggleBlockType(this.state.editorState, style);
  this.onChange(newState);
}

On doing this, I was getting an issue that, on clicking the buttons, the styles were not getting applied. After digging into it, I found that it was conflicting with my focus handler.

I was having the click event to get focus on editor on the root div element of the editor, which is also the parent of BlockStyleElements. To fix the issue, I shifted the event to a div on the Editor, below the BlockStyleElements.

This will apply the styles on clicking the buttons. Now, I have all the styles applied and editor works great. But I need to take its state out to the top level component so that I can save it and render it by passing it from there.

To take the state out, I will take the ‘ContentState’ of the element and convert it to a raw JSON object, which will be passed to the description property of the state. Apart from the JSON string, I will also take out the plain text so that I can work with it for showing lookup. The description property has to be updated every time the state changes. So let us pass the ‘descChange’ prop in the editor:

In Wisdom.js:

<WisdomEditor descChange = {(description, plainText) => this.props.descChange(description, plainText)}

In App.js:

<Wisdom descChange = {(description, plainText) => this.descChange(description, plainText)}

Now, the WisdomEditor, we need to pass description and plainText, whenever it is updated in the onChange handler:

onChange(editorState) {
  var currentContent = editorState.getCurrentContent();
  var rawContent = convertToRaw(currentContent);
  var description = JSON.stringify(rawContent)
}

Here, I used editorState to get the current content which is a ContentState object. It needs to be converted to a JSON object from the immutable object by using convertToRaw, which is imported in the file with this code:

import { Editor, EditorState, RichUtils, convertToRaw } from 'draft-js';

Then, I converted it to string form using JSON.stringify.

To get the plainText, I used the getPlainText function of ContentState.

var plainText = currentContent.getPlainText();

Once, I have the description(a JSON string) and plainText, I can pass it to ‘descChange’ handler available through props:

onChange(editorState) {
  var currentContent = editorState.getCurrentContent();
  var rawContent = convertToRaw(currentContent);
  var description = JSON.stringify(rawContent);

  var plainText = currentContent.getPlainText();
  
  this.props.descChange(description, plainText);
  this.setState({editorState});
}

Now in the descChange function in App.js, both description and plainText are available. I saved the description in the state so that I can save it later and used plainText to work with lookup:

descChange(description, plainText) {
  var addWisdom = Object.assign({}, this.state.addWisdom, {description: description});
  var lookups = findSimilarWisdom(plainText.trim());
  this.setState({addWisdom, lookups});
}

This will make the editor work with the app, and lets me save the description and create lookup rows.

Now, I need to pass the description to the editor. I will talk about it in my next post.