Draft-js highlight text

This is the sixth in an ongoing series of posts about draft-js.

  1. draft-js pieces
  2. draft-js EditorState
  3. draft-js ContentState
  4. draft-js ContentBlock
  5. draft-js SelectionState
  6. draft-js highlight text
  7. draft-js styling a ContentBlock

In this post I’ll look at adding some basic but super extensible functionality to a selection.  This is the first thing we tackled so that’s the order I’m going in :).

If you are using a wrapper like react-rte they you’ll have a toolbar that has some functionality like Bold, Italic, BulletList, NumberList, but not a lot.  You’ll quickly find some text decoration you need that is not there.  For us, we needed highlighting.

So the workflow would be

  • highlight some text with your mouse or keyboard
  • click a button
  • maybe get a popup with a color picker
  • the text background-color: myColor

Based on the first bullet point there, you can guess that we are going to grab a SelectionState.  But how?  Well,  it’s odd, but it seems that draft-js only exposes an onChange event.  I’m sure you could hack other events in and I may do that later but for now I just went with onChange.

The first thing you’ll want to do is make sure you are dealing with a selection so

isSelection = (editorState) => {
    const selection = editorState.getSelection();
    const start = selection.getStartOffset();
    const end = selection.getEndOffset();
    return start !== end;
};

onChange(editorState) {
  if (!this.isSelection(editorState)) {
    return;
  }
}

Now at least we know we are dealing with a selection of one or more characters.  The next step is to apply the modification.

import {RichUtils} from 'draft-js'

onChange(editorState) {
  if (!this.isSelection(editorState)) {
    return;
  }
  editorState = RichUtils.toggleInlineStyle(editorState, 'HIGHLIGHT');
}

RichUtils are/is a collection of helper functions provided by draft-js.  First toggleInlineStyles will look at the EditorState.getSelection() and look for HIGLIGHT and add it if it’s not there and remove it if it is.  yes.  toggle it.  Then it will return a new editorState.

But what the hell is HIGHTLIGHT?  Well you didn’t think it was gonna be that easy did you?  When you declare your Editor component you can pass it a collection of string/cssObject pairs using the property customStyleMap.

const styleMap = {
  'HIGHLIGHT': {
    backgroundColor: 'lightgreen'
   }
};
<Editor
  customStyleMap={styleMap} 
  editorState={this.state.editorState}
  ... /> 

now when you do

editorState = RichUtils.toggleInlineStyle(editorState, 'HIGHLIGHT');

you will be referencing that piece of reactified css.  What we did of course was to define colorHighlights so greenHighLight, blueHighLight etc so we could have multiple colors. The Editor’s styleMap has a number of default styles, Bold, Italic, Underline and Code.  You can use these the same way with toggleInlineStyles and you can override them with customStyleMap if you’d like them to look differently.

It seems like you might want to be able to do this even more dynamically so the consumer could click a color picker and use that.  I don’t know how to do that, but if I figure it out I’ll post about it.

But wait, you’re not done yet.  You have just mutated EditorState and need to locally persist that ( at least ) .  It depends on how you are storing state in your application, if you are storing it in redux then execute an action and pass the new EditorState in.  If you are handling it locally then do a setState({value: editorState}).  So the whole piece will look like this

isSelection = (editorState) => {
    const selection = editorState.getSelection();
    const start = selection.getStartOffset();
    const end = selection.getEndOffset();
    return start !== end;
};

onChange(editorState) {
  if (!this.isSelection(editorState)) {
    return;
  }
  editorState = RichUtils.toggleInlineStyle(editorState, 'HIGHLIGHT');
  // using concise obj prop notation short for {editorState:editorState}
  this.setState({editorState}); 
}
const styleMap = {
  'HIGHLIGHT': {
    backgroundColor: 'lightgreen'
   }
};

<Editor
  customStyleMap={styleMap} 
  editorState={this.state.editorState}
  ... /> 

That’s all there is too it.  You may wonder how to hook this up to a button elsewhere on the screen. Ultimately you are just acting on state which holds the EditorState.  The EditorState in the onChange event has the current SelectionState in it.  You can update state then act on that SelectionState elsewhere in your app.  When you pass it back to your app state it will render the subsequent changes.

 

Draft-js highlight text

Draft-js SelectionState

This is the first in an ongoing series of posts.

  1. draft-js pieces
  2. draft-js EditorState
  3. draft-js ContentState
  4. draft-js ContentBlock
  5. draft-js SelectionState
  6. draft-js highlight text
  7. draft-js styling a ContentBlock

This is the fifth in a series of posts about draft-js.  The first post was an overview of the pieces and subsequent posts have been a little deeper dive into said pieces.  This post will be about SelectionState.

SelectionState is essentially either the exact character spot where the cursor is blinking or a range of characters that have been highlighted.  You can get the SelectionState from various places as mentioned in previous posts i.e. EditorState, or ContentState.

In the DOM a selection is represented as an anchor and a focus point.  This representation ( and other aspects of the DOM selection ) are very helpful for rendering in the DOM, but not as necessary for the purposes of editing a document in code.  Thus draft-js has created a slightly different api.  It is comprised of key(s) and offset(s).  The key is the ContentBlock key where the point is positioned, and the offset value is the character offset in that block.  While the api also makes available the anchor and focus, you generally will be interacting with the key and offset via functions like these

const myStartKey = mySelection.getStartKey() // useful for finding the block that the selection starts in

const myStartOffset = mySelection.getStartOffset() // useful for finding the beginning of the selection.

corresponding End methods

This is really most of what’s in the SelectionState object.  Not much too it but it is super useful for decorating your document with different styles and functionality.  The workflow would be something like

I recognize that this is very dissatisfying as I’m just telling you about what pieces can change rather than how to actually change them and get the result you are after.  But now that this part is out of the way subsequent posts can deal with how to actually use these pieces to affect change.  Regrettably, but perhaps understandably, it’s not just as easy as grabbing an object and changing properties on it.  But there are lots of helper functions that allow you to do different things easily and we will be hit those next.

Draft-js SelectionState

Draft-js ContentBlock

This is the fourth in an ongoing series of posts.

  1. draft-js pieces
  2. draft-js EditorState
  3. draft-js ContentState
  4. draft-js ContentBlock
  5. draft-js SelectionState
  6. draft-js highlight text
  7. draft-js styling a ContentBlock

This is the fourth in a series of posts about draft-js.  The first post was an overview of the pieces and subsequent posts have been a little deeper dive into said pieces.  This post will be about ContentBlocks.

ContentBlocks are a course level building blocks of a draft-js document.  They correspond roughly to paragraphs or content blocks in html.  Ultimately they are comprised of a collection of characters and the metadata for each character.  That sounds pretty daunting, but it’s actually pretty cool.  They make heavy use/reuse of immutable.js.  For instance let’s say you have a 5000 character long document.  That’s one big collection.  However, if let’s say you have a letter “a” with no metadata on it, no styling or anything.  As you can imagine the letter “a” occurs rather frequently.  Rather that having a separate instance and memory allocation for each one, they all reference the same space.  So in total a very long document would just have, I don’t know 62 plus numbers and symbols objects in that list.  Then for each one that’s got a style that makes it blue for instance it does the same thing.  It’s pretty neat.

That said, I have not found myself interacting with the character map much yet.  But it’s neat to know what’s going on.  Most of what I’ve been doing I can scope to ContentBlocks and SelectionState.  The distinction being that you can style or wrap with functionality an entire block or a smaller chunk that is a selection of characters in a block or blocks.

A ContentBlock has some important initial properties to be aware of.  It has a key, which is essentially an id and allows you to find/access the block.  To wit there is

const blockKey = myBlock.getKey()

It also has a type with corresponding

const blockType = myblock.getType()

Your default ContentBlock has a type of “unstyled”.  Out of the box draft-js has a few, what they call DraftBlockType(s) such as LTR/RTL (left to right/right to left), support for bullet lists and a few others. You can also create your own types then can then be easily associated with a block.  Here is a good explanation of that.  You may want a code block type so you don’t have to use the Quote style to write code in your block like my stupid blog, for instance.

You can get the text, the characterlist ( as mentioned above ), the length.  And you can get a list of styles that have been applied to a certain range.  These would be what are called inline styles that are not block scoped but rather selection scoped.

My next post will be a short post about SelectionState and then we can get into how to actually interact with these pieces to make the fun stuff that has been hinted at actually happen.

Draft-js ContentBlock

Draft-js ContentState

This is the third in an ongoing series of posts.

  1. draft-js pieces
  2. draft-js EditorState
  3. draft-js ContentState
  4. draft-js ContentBlock
  5. draft-js SelectionState
  6. draft-js highlight text
  7. draft-js styling a ContentBlock

This is the third in a series of posts about draft-js.  The first post was an overview of the pieces and subsequent posts have been a little deeper dive into said pieces.  This post will be about ContentState.

The last post was about EditorState, The EditorState essentially maintains a stack of ContentState objects.  Each representing a subsequent change in state.  As I mentioned in that post it also has some convenience functions for accessing the current ContentState including

const myCurrentContent = myEditorState.getCurrentContent()

What data ContentState holds is not that extensive, it has a block map ( a collection of ContentBlocks ) a beforeSelection and an afterSelection both returning a SelectionState object.  But it does have a lot of functions that allow you to access the pieces of the document, namely the ContentBlocks.

So for instance, you can

const targetBlock = myCurrentContent.getBlockForKey(myTargetKey);

which returns a specific ContentBlock, you can then interrogate it or use it as a target to modify.  That’s odd wording but basically everything is immutable, so most of the top level draft-js objects don’t provide set methods.  You generally take a piece you want to modify and do so via the immutable.js api or via functions provided by draft-js.  These pieces are generally either a selection or whole ContentBlock.  We will very soon be getting into those actions as that’s really the fun stuff, but you must understand the pieces before the modifying helper functions really make sense.

So the ContentState is a snapshot of the state of your document with methods to help you find what you are looking for in the document.  The most basic of which is getBlockForKey(key).  It seems that they provide a very nice feature of letting you compare the previous snapshot to the current snapshot via getBefore() and getAfter() methods. e.g.

getKeyBefore()/getKeyAfter()

getBlockBefore()/getBlockAfter()

Also

getFirstBlock()/getLastBlock()

Regrettably I don’t really know the inner workings of this but I presume the getBefore is the last ContentState and after is the current ContentState.  Maybe a reader can help me out.

In any case this is the bulk of the api on ContentState.  I’m not going to go over every function as the docs do a nice job of that.  I’m just going to say that if you need a specific block or selection, and/or you want to compare what it was to what it is, then you want to come to the ContentState.

Draft-js ContentState

Draft-js EditorState

This is the second in an ongoing series of posts.

  1. draft-js pieces
  2. draft-js EditorState
  3. draft-js ContentState
  4. draft-js ContentBlock
  5. draft-js SelectionState
  6. draft-js highlight text
  7. draft-js styling a ContentBlock

In my last post I did a bit of an overview of most of the common pieces that go into using draft.js.  Today I will write about them independently, hopefully providing more information.

The Draft-js component is called Editor.js and serves as a location to set the initial state of the component.  At a minimum it takes an EditorState and an onChange callback.  If your Editor is to be blank you can call

const myEditorState = EditorState.createEmpty()

which is a static method off of EditorState.

State of your document

However you chose to maintain the state in your react app, the state you maintain for the document is the EditorState.  The signature for the onChange event is

onChange(editorState)

so you receive EditorState and this is your window into draft.js  with your instance of EditorState you can effect the state of your document in many different ways.  There are a number of static functions found through out draft.js and most of them act on an EditorState.  Thus, you want to keep your EditorState in your applications state management.

Persisting and Recreating a document

If you are have a previously saved document, the chances are you saved it using

const myRawState = convertToRaw(myEditorState)

which is a function that you import from draft-js.  This call takes the rather large instance of EditorState and strips it down to just a representation of the ContentBlocks.  It strips out the per character metadata, the undo/redo history, selections, decorators etc. and leaves you with just enough to recreate the document completely.  You can then persist this object in a database.

When you want to bring the document back you would then similarly call convertFromRaw which you import from draft-js.

const myContentState = convertFromRaw(myRawState)

The result of this is an instance of ContentState.  ContentState is essentially the EditorState without the undo/redo, selection etc.  It doesn’t have the historical or contextual date I guess you’d say.  Once you have the ContentState you can call the static method createWithContent off of EditorState.

const myEditorState = EditorState createWithContent(myContentState)

This final call gives you a proper instance of an EditorState that you can then pass into your Editor.js component.

Selections

The EditorState also has some methods to help you interact with the document.  It has a method

const mySelectionState = myEditorState.getSelection()

The SelectionState has information about the currently selected section of the document.  If your cursor is just blinking, then it will contain an startingOffset and endingOffset that are the same and correspond to the number of characters from the beginning of the ContentBlock. If, however, you have selected a portion of text the SelectionState startingOffset and endingOffset will be the character position of the first character in the selection and the last character in the selection relative to the beginning of the content block.

I wont go into too much detail about SelectionState here, but essentially a selection is one of the two main ways of interacting with the document, the other being via ContentBlocks which roughly correspond to paragraphs.

Another method on EditorState is

const myCurrentInlineStyle = myEditorState.getCurrentInlineStyle

myCurrentInlineStyle will be a collection of strings that represent the inlineStyles that will be applied to the next character that is typed.

You can use this to assess the current style of an area of selected text.  By looking at the collection you can tell what state of styling is being applied to the area and you can add or remove inlineStyles to the selection.  This is more complicated than I’m making it sound, and you’ll want to read my post about SelectionState as it will go into much more detail.

Lastly

The last two methods that I will go over and the last two instance methods on the EditorState are

const myCurrentContent = myEditorState.getCurrentContent()

and

const myContentBlocks = myEditorState.getBlockTree()

I will go into ContentState more in the next post but it is essentially a more targeted version of the EditorState if you will.  It has many more instance methods for interacting with ContentBlocks and Selections.  Generally you will interact with those two parts of the document via the ContentState but the getCurrentSelection() and getBlockTree() give you two convenient methods for checking what you have.  e.g. check if your selection is a span of more than one 0 characters and if so get the ContentState and do something.

The BlockTree is a collection that represents all of the ContentBlocks in your document.  Again the ContentState is a finer grained tool for dealing with these but if you just need some information about the blocks this is a convenient function.

Summary

That’s all I can stand to type today.  But to summarize, the Editor.js Component takes an EditorState object to instantiate.  That object can either be an empty EditorState via the EditorState.createEmpty() method, or one you have reinstated from a persisted copy of the raw ContentState via the imported functions convertToRaw() and convertFromRaw()

Once you have your EditorState you have 4 instance methods to use.

  • getCurrentContent() which retrieves the ContentState a finer grained tool for manipulating the document
  • getSelection() which retrieves the SelectionState which represents the characters that have been selected in the document or just the location of the cursor if none have been selected
  • getCurrentInlineStyle() which returns a collection of the styles that have been applied to the selection or if no selection the styles that will be applied to the next character typed
  • getBlockTree() which returns a collection of the ContentBlocks that make up your document.

Next time I will go over the ContentState which I hope will be shorter 🙂

 

Draft-js EditorState

Draft-js pieces

This is the first in an ongoing series of posts.

  1. draft-js pieces
  2. draft-js EditorState
  3. draft-js ContentState
  4. draft-js ContentBlock
  5. draft-js SelectionState
  6. draft-js highlight text
  7. draft-js styling a ContentBlock

Draft-js is a highly programmatic rich text editor created by facebook.  But it’s not what you might expect.Draft-js is just the underpinnings, leaving everything, and I mean everything, else up to you.  When you render an Editor you get a textbox.  This is because facebook wanted rich text in a number of different contexts, like all that commenting crap they do.  I don’t know.

There are several wrappers that provide some structure and basic stuff like a toolbar.  The one I use, and am extending for my purposes is react-rte.  It’s pretty good and will get you started.

This post is about the api and the pieces you will interact with when using Draft-js or a wrapper for it.

There is, of course, the Editor Component.  This is your window into the api.  Or maybe your blender where you put all your margarita ingredients.  But the meat and potatoes is in an object that is called EditorState.  The EditorState is the complete representation of your document.  It is wildly … large? bloated? If you look at it in a console.log it’s just huge, but that is not a problem, because most of it is empty arrays and they rely heavily on immutable.js for reuse and performance.  I just mention it because you’re not going to want to persist that.

EditorState provides a convertToRaw() and a convertFromRaw() function that will distill it down to just what you need.  This is what you are going to want to persist.  It’s still pretty complex but it’s the minimum representation of your document.

Draft-js uses immutable.js heavily, so pretty much all collections are immutable.js maps. EditorState is made up of a map of ( generally ) 7 things.  The first and most important is CurrentState.  It’s actually called CurrentContent but it an object called CurrentState.  The other elements are things such as undoStack, redoStack, decorators, selections etc.  All very cool stuff which hopefully I’ll go into in subsequent posts.

CurrentState is essentially a map of three things, the blockmap which is a  collection of ContentBlocks and the before and after state of the current selection.  And now we are getting somewhere.  The ContentBlocks represent line break delineated string.  Generally this is a paragraph in your document.  This (besides the selection) is the main building block of a document. ContentBlocks are generally of a type “unstyled”.  You can of course style a block.  This consists of, essentially, adding css styles the containing element.  You can do this on the fly, meaning just a one off or you can create custom bocktypes that you can reuse such as a codeBlock that will be styled in a nice way for displaying code.  Then you can start or apply that CodeBlock for specific a result.

Because a ContentBlocks is basically a string, you can reference any part of it by the number of characters.  Remember how above I’ve mentioned Selections  a couple times?  Well this is where selections come in.  If for instance you highlight some text, an onchange will fire and by calling EditorState.getSelection() You’ll get a SelectionState.  I wont explain all the pieces the docs do a great job of that, but basically you get a startOffset and an endOffset.  More importantly you get the SelectionState object which you can use to apply inline styles to.Draft-js will wrap the content in a span ( actually two spans ) and you can apply a previously defined style set to the wrapping span.  Again I will go into detail later, but this is super powerful and what makes it even more powerful is that you can wrap a selection in a different tag, or even your own react component that you have complete control over.

That’s enough for this post but to reiterate, we have

 

Draft-js pieces