From 0a6ea32e73d97f66e926ff2b5e6ec812ae97859e Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Sat, 10 Aug 2019 11:15:51 +0100
Subject: [PATCH 01/16] Remove dead code in onSave test case

---
 lib/components/DraftailEditor.test.js | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/lib/components/DraftailEditor.test.js b/lib/components/DraftailEditor.test.js
index 5f494dce..92070600 100644
--- a/lib/components/DraftailEditor.test.js
+++ b/lib/components/DraftailEditor.test.js
@@ -284,10 +284,6 @@ describe("DraftailEditor", () => {
     wrapper.instance().saveState();
 
     expect(onSave).toHaveBeenCalled();
-
-    shallowNoLifecycle(<DraftailEditor />)
-      .instance()
-      .saveState();
   });
 
   it("#stateSaveInterval", () => {

From 5de0849c0329152c87ddc4cd3e090a089cd891db Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Sat, 10 Aug 2019 13:09:40 +0100
Subject: [PATCH 02/16] Add editorState and onChange props to make the editor
 controlled

---
 lib/components/DraftailEditor.js      | 105 +++++++++++++++----------
 lib/components/DraftailEditor.test.js | 106 +++++++++++++++++++-------
 2 files changed, 142 insertions(+), 69 deletions(-)

diff --git a/lib/components/DraftailEditor.js b/lib/components/DraftailEditor.js
index 4acf2c25..7de1c78d 100644
--- a/lib/components/DraftailEditor.js
+++ b/lib/components/DraftailEditor.js
@@ -45,8 +45,12 @@ type ControlProp = {|
 |};
 
 type Props = {|
+  // Uncontrolled component props.
   rawContentState: ?RawDraftContentState,
   onSave: ?(content: null | RawDraftContentState) => void,
+  // Controlled component props.
+  editorState: ?EditorState,
+  onChange: ?(editorState: EditorState) => void,
   onFocus: ?() => void,
   onBlur: ?() => void,
   placeholder: ?string,
@@ -110,7 +114,7 @@ type Props = {|
   bottomToolbar: ?ComponentType<ToolbarProps>,
   // Max level of nesting for list items. 0 = no nesting. Maximum = 10.
   maxListNesting: number,
-  // Frequency at which to call the save callback (ms).
+  // Frequency at which to call the onSave callback (ms).
   stateSaveInterval: number,
 |};
 
@@ -119,6 +123,10 @@ const defaultProps = {
   rawContentState: null,
   // Called when changes occured. Use this to persist editor content.
   onSave: null,
+  // Content of the editor, when using the editor as a controlled component. Incompatible with `rawContentState`.
+  editorState: null,
+  // Called whenever the editor state is updated. Use this to store the content of a controlled editor.
+  onChange: null,
   // Called when the editor receives focus.
   onFocus: null,
   // Called when the editor loses focus.
@@ -181,7 +189,8 @@ const defaultProps = {
 };
 
 type State = {|
-  editorState: EditorState,
+  // editorState is only part of the local state if the editor is uncontrolled.
+  editorState?: EditorState,
   hasFocus: boolean,
   readOnlyState: boolean,
   sourceOptions: ?{
@@ -207,13 +216,13 @@ class DraftailEditor extends Component<Props, State> {
   /* :: updateTimeout: ?number; */
   /* :: lockEditor: () => void; */
   /* :: unlockEditor: () => void; */
+  /* :: getEditorState: () => EditorState; */
 
   constructor(props: Props) {
     super(props);
 
     this.onChange = this.onChange.bind(this);
     this.saveState = this.saveState.bind(this);
-    this.getEditorState = this.getEditorState.bind(this);
 
     this.toggleSource = this.toggleSource.bind(this);
     this.toggleEditor = this.toggleEditor.bind(this);
@@ -247,14 +256,29 @@ class DraftailEditor extends Component<Props, State> {
 
     this.renderSource = this.renderSource.bind(this);
 
-    const { rawContentState } = props;
+    const { editorState: editorStateProp, rawContentState } = props;
 
     this.state = {
-      editorState: conversion.createEditorState(rawContentState),
       readOnlyState: false,
       hasFocus: false,
       sourceOptions: null,
     };
+
+    // If editorState is not used as a prop, create it in local state from rawContentState.
+    if (editorStateProp !== null) {
+      this.getEditorState = (): EditorState => {
+        const { editorState } = this.props;
+        // $FlowFixMe If the prop is set at initialisation, assume it is going to be set later too.
+        return editorState;
+      };
+    } else {
+      this.state.editorState = conversion.createEditorState(rawContentState);
+      this.getEditorState = (): EditorState => {
+        const { editorState } = this.state;
+        // $FlowFixMe If editorState is stored locally just above, we can assume it is accessible there.
+        return editorState;
+      };
+    }
   }
 
   componentDidMount() {
@@ -294,7 +318,7 @@ class DraftailEditor extends Component<Props, State> {
   /* :: onTab: (event: SyntheticKeyboardEvent<>) => true; */
   onTab(event: SyntheticKeyboardEvent<>) {
     const { maxListNesting } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     const newState = RichUtils.onTab(event, editorState, maxListNesting);
 
     this.onChange(newState);
@@ -311,8 +335,9 @@ class DraftailEditor extends Component<Props, State> {
       blockTypes,
       inlineStyles,
       entityTypes,
+      onChange,
     } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     const shouldFilterPaste =
       nextState.getCurrentContent() !== editorState.getCurrentContent() &&
       nextState.getLastChangeType() === "insert-fragment";
@@ -332,24 +357,28 @@ class DraftailEditor extends Component<Props, State> {
       );
     }
 
-    this.setState(
-      {
-        editorState: filteredState,
-      },
-      () => {
-        window.clearTimeout(this.updateTimeout);
-        this.updateTimeout = window.setTimeout(
-          this.saveState,
-          stateSaveInterval,
-        );
-      },
-    );
+    if (onChange) {
+      onChange(filteredState);
+    } else {
+      this.setState(
+        {
+          editorState: filteredState,
+        },
+        () => {
+          window.clearTimeout(this.updateTimeout);
+          this.updateTimeout = window.setTimeout(
+            this.saveState,
+            stateSaveInterval,
+          );
+        },
+      );
+    }
   }
 
   /* :: onEditEntity: (entityKey: string) => void; */
   onEditEntity(entityKey: string) {
     const { entityTypes } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     const content = editorState.getCurrentContent();
     const entity = content.getEntity(entityKey);
     const entityType = entityTypes.find((t) => t.type === entity.type);
@@ -374,7 +403,7 @@ class DraftailEditor extends Component<Props, State> {
   /* :: onRemoveEntity: (entityKey: string, blockKey: string) => void; */
   onRemoveEntity(entityKey: string, blockKey: string) {
     const { entityTypes } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     const content = editorState.getCurrentContent();
     const entity = content.getEntity(entityKey);
     const entityType = entityTypes.find((t) => t.type === entity.type);
@@ -397,7 +426,7 @@ class DraftailEditor extends Component<Props, State> {
 
   /* :: onUndoRedo: (type: string) => void; */
   onUndoRedo(type: string) {
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     let newEditorState = editorState;
 
     if (type === UNDO_TYPE) {
@@ -411,7 +440,7 @@ class DraftailEditor extends Component<Props, State> {
 
   /* :: onRequestSource: (entityType: string) => void; */
   onRequestSource(entityType: string) {
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     const contentState = editorState.getCurrentContent();
     const entityKey = DraftUtils.getSelectionEntity(editorState);
 
@@ -452,16 +481,10 @@ class DraftailEditor extends Component<Props, State> {
     });
   }
 
-  /* :: getEditorState: () => EditorState; */
-  getEditorState() {
-    const { editorState } = this.state;
-    return editorState;
-  }
-
   /* :: saveState: () => void; */
   saveState() {
     const { onSave } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
 
     if (onSave) {
       onSave(conversion.serialiseEditorState(editorState));
@@ -493,7 +516,7 @@ class DraftailEditor extends Component<Props, State> {
   /* :: handleReturn: (e: SyntheticKeyboardEvent<>) => 'not-handled' | 'handled'; */
   handleReturn(e: SyntheticKeyboardEvent<>) {
     const { enableLineBreak, inlineStyles } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     let ret = NOT_HANDLED;
 
     // alt + enter opens links and other entities with a `url` property.
@@ -560,7 +583,7 @@ class DraftailEditor extends Component<Props, State> {
   /* :: handleKeyCommand: (command: DraftEditorCommand) => 'handled' | 'not-handled'; */
   handleKeyCommand(command: DraftEditorCommand) {
     const { entityTypes, blockTypes, inlineStyles } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
 
     if (entityTypes.some((t) => t.type === command)) {
       this.onRequestSource(command);
@@ -604,7 +627,7 @@ class DraftailEditor extends Component<Props, State> {
   /* :: handleBeforeInput: (char: string) => 'handled' | 'not-handled'; */
   handleBeforeInput(char: string) {
     const { blockTypes, inlineStyles, enableHorizontalRule } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     const selection = editorState.getSelection();
 
     if (selection.isCollapsed()) {
@@ -678,32 +701,32 @@ class DraftailEditor extends Component<Props, State> {
 
   /* :: toggleBlockType: (blockType: string) => void; */
   toggleBlockType(blockType: string) {
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     this.onChange(RichUtils.toggleBlockType(editorState, blockType));
   }
 
   /* :: toggleInlineStyle: (inlineStyle: string) => void; */
   toggleInlineStyle(inlineStyle: string) {
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     this.onChange(RichUtils.toggleInlineStyle(editorState, inlineStyle));
   }
 
   /* :: addHR: () => void; */
   addHR() {
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     this.onChange(DraftUtils.addHorizontalRuleRemovingSelection(editorState));
   }
 
   /* :: addBR: () => void; */
   addBR() {
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     this.onChange(DraftUtils.addLineBreak(editorState));
   }
 
   /* :: blockRenderer: (block: ContentBlock) => {}; */
   blockRenderer(block: ContentBlock) {
     const { entityTypes } = this.props;
-    const { editorState } = this.state;
+    const editorState = this.getEditorState();
     const contentState = editorState.getCurrentContent();
 
     if (block.getType() !== BLOCK_TYPE.ATOMIC) {
@@ -770,7 +793,8 @@ class DraftailEditor extends Component<Props, State> {
 
   /* :: renderSource: () => ?Node; */
   renderSource() {
-    const { editorState, sourceOptions } = this.state;
+    const { sourceOptions } = this.state;
+    const editorState = this.getEditorState();
 
     if (sourceOptions && sourceOptions.entityType) {
       const Source = sourceOptions.entityType.source;
@@ -822,7 +846,8 @@ class DraftailEditor extends Component<Props, State> {
       topToolbar,
       bottomToolbar,
     } = this.props;
-    const { editorState, hasFocus, readOnlyState } = this.state;
+    const { hasFocus, readOnlyState } = this.state;
+    const editorState = this.getEditorState();
     const isReadOnly = readOnlyState || readOnly;
     const hidePlaceholder = DraftUtils.shouldHidePlaceholder(editorState);
     const entityDecorators = entityTypes
diff --git a/lib/components/DraftailEditor.test.js b/lib/components/DraftailEditor.test.js
index 92070600..cb3aa2d3 100644
--- a/lib/components/DraftailEditor.test.js
+++ b/lib/components/DraftailEditor.test.js
@@ -27,6 +27,26 @@ describe("DraftailEditor", () => {
     expect(shallowNoLifecycle(<DraftailEditor />)).toMatchSnapshot();
   });
 
+  it("#rawContentState sets local state", () => {
+    const wrapper = shallowNoLifecycle(
+      <DraftailEditor
+        rawContentState={{
+          entityMap: {},
+          blocks: [{ text: "test" }],
+        }}
+      />,
+    );
+    expect(wrapper.state("editorState")).toBeInstanceOf(EditorState);
+  });
+
+  it("#editorState is passed through", () => {
+    const editorState = EditorState.createEmpty();
+    const wrapper = shallowNoLifecycle(
+      <DraftailEditor editorState={editorState} />,
+    );
+    expect(wrapper.find("PluginEditor").prop("editorState")).toBe(editorState);
+  });
+
   it("editorRef", () => {
     expect(mount(<DraftailEditor />).instance().editorRef).toBeDefined();
   });
@@ -77,17 +97,7 @@ describe("DraftailEditor", () => {
             placeholder="Write here…"
             rawContentState={{
               entityMap: {},
-              blocks: [
-                {
-                  key: "b3kdk",
-                  text: "test",
-                  type: "header-two",
-                  depth: 0,
-                  inlineStyleRanges: [],
-                  entityRanges: [],
-                  data: {},
-                },
-              ],
+              blocks: [{ text: "test" }],
             }}
           />,
         )
@@ -277,13 +287,36 @@ describe("DraftailEditor", () => {
     ).toMatchSnapshot();
   });
 
-  it("#onSave", () => {
-    const onSave = jest.fn();
-    const wrapper = shallowNoLifecycle(<DraftailEditor onSave={onSave} />);
+  describe("#onSave", () => {
+    it("works", () => {
+      jest.useFakeTimers();
+
+      const onSave = jest.fn();
+      const wrapper = shallowNoLifecycle(
+        <DraftailEditor onSave={onSave} stateSaveInterval={100} />,
+      );
+
+      wrapper.instance().onChange(EditorState.createEmpty());
+
+      jest.advanceTimersByTime(150);
+
+      expect(onSave).toHaveBeenCalled();
+    });
+
+    it("does not get called when onChange is used", () => {
+      jest.useFakeTimers();
 
-    wrapper.instance().saveState();
+      const onSave = jest.fn();
+      const wrapper = shallowNoLifecycle(
+        <DraftailEditor onSave={onSave} onChange={() => {}} />,
+      );
+
+      wrapper.instance().onChange(EditorState.createEmpty());
 
-    expect(onSave).toHaveBeenCalled();
+      jest.advanceTimersByTime(1000);
+
+      expect(onSave).not.toHaveBeenCalled();
+    });
   });
 
   it("#stateSaveInterval", () => {
@@ -390,27 +423,42 @@ describe("DraftailEditor", () => {
       const onSave = jest.fn();
       const wrapper = shallowNoLifecycle(<DraftailEditor onSave={onSave} />);
 
-      const contentState = convertFromRaw({
-        entityMap: {},
-        blocks: [{ text: "test" }],
-      });
+      wrapper.instance().onChange(EditorState.createEmpty());
+      jest.advanceTimersByTime(1000);
+      expect(onSave).toHaveBeenCalled();
+    });
 
-      const editorState = EditorState.push(
-        EditorState.createEmpty(),
-        contentState,
-        "insert-fragment",
+    it("calls onChange if used instead", () => {
+      jest.useFakeTimers();
+
+      const onSave = jest.fn();
+      const onChange = jest.fn();
+      const wrapper = shallowNoLifecycle(
+        <DraftailEditor onSave={onSave} onChange={onChange} />,
       );
 
-      wrapper.instance().onChange(editorState);
+      wrapper.instance().onChange(EditorState.createEmpty());
       jest.advanceTimersByTime(1000);
-      expect(onSave).toHaveBeenCalled();
+      expect(onSave).not.toHaveBeenCalled();
+      expect(onChange).toHaveBeenCalled();
     });
   });
 
-  it("getEditorState", () => {
-    const wrapper = shallowNoLifecycle(<DraftailEditor />);
+  describe("getEditorState", () => {
+    it("works with uncontrolled editor", () => {
+      const wrapper = shallowNoLifecycle(<DraftailEditor />);
+
+      expect(wrapper.instance().getEditorState()).toBeInstanceOf(EditorState);
+    });
 
-    expect(wrapper.instance().getEditorState()).toBeInstanceOf(EditorState);
+    it("works with controlled editor", () => {
+      const editorState = EditorState.createEmpty();
+      const wrapper = shallowNoLifecycle(
+        <DraftailEditor editorState={editorState} />,
+      );
+
+      expect(wrapper.instance().getEditorState()).toBe(editorState);
+    });
   });
 
   describe("handleReturn", () => {

From f6ca6cd5a16f50dceb5e2c143abd70d14ff58562 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Sun, 11 Aug 2019 11:16:48 +0100
Subject: [PATCH 03/16] Add demo of controlled editor

---
 examples/docs.story.js | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/examples/docs.story.js b/examples/docs.story.js
index c467ce7d..4050db2b 100644
--- a/examples/docs.story.js
+++ b/examples/docs.story.js
@@ -1,8 +1,8 @@
 import { storiesOf } from "@storybook/react";
-import React from "react";
+import React, { useState } from "react";
 import { injectIntl } from "react-intl";
 import { convertFromHTML, convertToHTML } from "draft-convert";
-import { convertToRaw, convertFromRaw } from "draft-js";
+import { EditorState, convertToRaw, convertFromRaw } from "draft-js";
 import { Formik } from "formik";
 
 import { DraftailEditor, INLINE_STYLE, ENTITY_TYPE, BLOCK_TYPE } from "../lib";
@@ -22,6 +22,8 @@ import PrismDecorator from "./components/PrismDecorator";
 import ReadingTime from "./components/ReadingTime";
 
 storiesOf("Docs", module)
+  // Add a decorator rendering story as a component for hooks support.
+  .addDecorator((Story) => <Story />)
   .add("Built-in formats", () => (
     <EditorWrapper
       id="docs-built-in-formats"
@@ -495,4 +497,17 @@ storiesOf("Docs", module)
         </form>
       )}
     </Formik>
-  ));
+  ))
+  .add("Controlled component", () => {
+    const [editorState, setEditorState] = useState(EditorState.createEmpty());
+    return (
+      <EditorWrapper
+        id="controlled-component"
+        editorState={editorState}
+        onChange={setEditorState}
+        entityTypes={[ENTITY_CONTROL.LINK]}
+        blockTypes={[BLOCK_CONTROL.UNORDERED_LIST_ITEM]}
+        inlineStyles={[INLINE_CONTROL.BOLD]}
+      />
+    );
+  });

From d272345f8e2850e77e89f30647ced2cc5257d072 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Mon, 12 Aug 2019 00:05:20 +0100
Subject: [PATCH 04/16] Make EditorWrapper compatible with a controlled editor

---
 examples/components/EditorWrapper.js | 61 ++++++++++++++++++++++------
 1 file changed, 49 insertions(+), 12 deletions(-)

diff --git a/examples/components/EditorWrapper.js b/examples/components/EditorWrapper.js
index aa5dfb2f..0ca4ca56 100644
--- a/examples/components/EditorWrapper.js
+++ b/examples/components/EditorWrapper.js
@@ -1,5 +1,6 @@
 // @flow
 import React, { Component } from "react";
+import { EditorState, convertToRaw } from "draft-js";
 import type { RawDraftContentState } from "draft-js/lib/RawDraftContentState";
 
 import { DraftailEditor } from "../../lib";
@@ -14,7 +15,10 @@ const DRAFTAIL_VERSION =
 
 type Props = {|
   id: string,
-  onSave: ?(?RawDraftContentState) => void,
+  rawContentState: ?RawDraftContentState,
+  editorState: ?EditorState,
+  onSave: ?(content: null | RawDraftContentState) => void,
+  onChange: ?(editorState: EditorState) => void,
 |};
 
 type State = {|
@@ -32,10 +36,11 @@ class EditorWrapper extends Component<Props, State> {
     };
 
     this.onSave = this.onSave.bind(this);
+    this.onChange = this.onChange.bind(this);
   }
 
-  /* :: onSave: (content: ?RawDraftContentState) => void; */
-  onSave(content: ?RawDraftContentState) {
+  /* :: onSave: (content: null | RawDraftContentState) => void; */
+  onSave(content: null | RawDraftContentState) {
     const { id, onSave } = this.props;
 
     this.setState(({ saveCount }) => ({ content, saveCount: saveCount + 1 }));
@@ -47,19 +52,48 @@ class EditorWrapper extends Component<Props, State> {
     }
   }
 
+  /* :: onChange: (nextState: EditorState) => void; */
+  onChange(nextState: EditorState) {
+    const { id, onChange } = this.props;
+    const content = convertToRaw(nextState.getCurrentContent());
+
+    this.setState(({ saveCount }) => ({ content, saveCount: saveCount + 1 }));
+
+    sessionStorage.setItem(`${id}:content`, JSON.stringify(content));
+
+    if (onChange) {
+      onChange(nextState);
+    }
+  }
+
   render() {
-    const { id, onSave, ...editorProps } = this.props;
+    const {
+      id,
+      editorState,
+      rawContentState,
+      onSave,
+      onChange,
+      ...editorProps
+    } = this.props;
     const { content, saveCount } = this.state;
-    const storedContent = sessionStorage.getItem(`${id}:content`) || null;
-    const initialContent = storedContent ? JSON.parse(storedContent) : null;
+    const dataProps = {};
+    let initialContent;
+
+    if (editorState) {
+      dataProps.editorState = editorState;
+      dataProps.onChange = this.onChange;
+    } else {
+      const storedContent = sessionStorage.getItem(`${id}:content`) || "null";
+      initialContent =
+        rawContentState || storedContent ? JSON.parse(storedContent) : null;
+      dataProps.rawContentState = initialContent;
+      dataProps.onSave = this.onSave;
+    }
+
     return (
       <div className={`EditorWrapper EditorWrapper--${id}`}>
         <SentryBoundary>
-          <DraftailEditor
-            rawContentState={initialContent}
-            {...editorProps}
-            onSave={this.onSave}
-          />
+          <DraftailEditor {...dataProps} {...editorProps} />
         </SentryBoundary>
         <details className="EditorWrapper__details">
           <summary>
@@ -73,7 +107,10 @@ class EditorWrapper extends Component<Props, State> {
               <span>{`Saves: ${saveCount}`}</span>
             </li>
           </ul>
-          <EditorBenchmark componentProps={this.props} runOnMount />
+          {/* Running multiple editors with the same base state is a source of issues. */}
+          {editorState ? null : (
+            <EditorBenchmark componentProps={this.props} runOnMount />
+          )}
           <Highlight
             value={JSON.stringify(content || initialContent, null, 2)}
           />

From ea4b51cdcabbed094a3bc8c10c6f72b8353f0a02 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Mon, 12 Aug 2019 00:10:20 +0100
Subject: [PATCH 05/16] Split editorState getters into separate functions to
 avoid typing issues

---
 lib/components/DraftailEditor.js | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/lib/components/DraftailEditor.js b/lib/components/DraftailEditor.js
index 7de1c78d..92870c57 100644
--- a/lib/components/DraftailEditor.js
+++ b/lib/components/DraftailEditor.js
@@ -266,18 +266,10 @@ class DraftailEditor extends Component<Props, State> {
 
     // If editorState is not used as a prop, create it in local state from rawContentState.
     if (editorStateProp !== null) {
-      this.getEditorState = (): EditorState => {
-        const { editorState } = this.props;
-        // $FlowFixMe If the prop is set at initialisation, assume it is going to be set later too.
-        return editorState;
-      };
+      this.getEditorState = this.getEditorStateProp.bind(this);
     } else {
       this.state.editorState = conversion.createEditorState(rawContentState);
-      this.getEditorState = (): EditorState => {
-        const { editorState } = this.state;
-        // $FlowFixMe If editorState is stored locally just above, we can assume it is accessible there.
-        return editorState;
-      };
+      this.getEditorState = this.getEditorStateState.bind(this);
     }
   }
 
@@ -481,6 +473,18 @@ class DraftailEditor extends Component<Props, State> {
     });
   }
 
+  /* :: getEditorStateProp: () => EditorState; */
+  getEditorStateProp() {
+    const { editorState } = this.props;
+    return editorState;
+  }
+
+  /* :: getEditorStateState: () => EditorState; */
+  getEditorStateState() {
+    const { editorState } = this.state;
+    return editorState;
+  }
+
   /* :: saveState: () => void; */
   saveState() {
     const { onSave } = this.props;

From 6e99f70167f9590caed1d79e078886dc3062f33a Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Tue, 13 Aug 2019 13:43:50 +0100
Subject: [PATCH 06/16] Update to latest version of draftjs-conductor

---
 package-lock.json | 88 +++++++++++++++++++++++++++++++++--------------
 package.json      |  2 +-
 2 files changed, 64 insertions(+), 26 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 26f5d847..3445b56f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10123,9 +10123,9 @@
       }
     },
     "draftjs-conductor": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/draftjs-conductor/-/draftjs-conductor-0.4.1.tgz",
-      "integrity": "sha512-5BcJLdYLNIA/TNp/9xwIeD1quWsWEoi0ZI81TiW3vLecBEQgWKVxuKZaLdaXHYpW4/kdQvAJz2KcOBTpJkYBxg=="
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/draftjs-conductor/-/draftjs-conductor-0.5.2.tgz",
+      "integrity": "sha512-g7j7Kjxr5wIAXnS6bD/yDf3gq7FpfiHmnp5RFgjw+54GGN6EIO1ABh/OCw9LAU/xTCJ9dHIGYLHStR0KWUic+Q=="
     },
     "draftjs-filters": {
       "version": "2.2.3",
@@ -12206,7 +12206,8 @@
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -12227,12 +12228,14 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -12247,17 +12250,20 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -12374,7 +12380,8 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -12386,6 +12393,7 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -12400,6 +12408,7 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -12407,12 +12416,14 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.2.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -12431,6 +12442,7 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -12511,7 +12523,8 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -12523,6 +12536,7 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -12608,7 +12622,8 @@
         "safe-buffer": {
           "version": "5.1.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -12644,6 +12659,7 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -12663,6 +12679,7 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -12706,12 +12723,14 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.2",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -23684,7 +23703,8 @@
             "ansi-regex": {
               "version": "2.1.1",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "aproba": {
               "version": "1.2.0",
@@ -23705,12 +23725,14 @@
             "balanced-match": {
               "version": "1.0.0",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "brace-expansion": {
               "version": "1.1.11",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -23725,17 +23747,20 @@
             "code-point-at": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "concat-map": {
               "version": "0.0.1",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "console-control-strings": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "core-util-is": {
               "version": "1.0.2",
@@ -23852,7 +23877,8 @@
             "inherits": {
               "version": "2.0.3",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "ini": {
               "version": "1.3.5",
@@ -23864,6 +23890,7 @@
               "version": "1.0.0",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "number-is-nan": "^1.0.0"
               }
@@ -23878,6 +23905,7 @@
               "version": "3.0.4",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "brace-expansion": "^1.1.7"
               }
@@ -23885,12 +23913,14 @@
             "minimist": {
               "version": "0.0.8",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "minipass": {
               "version": "2.2.4",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "safe-buffer": "^5.1.1",
                 "yallist": "^3.0.0"
@@ -23909,6 +23939,7 @@
               "version": "0.5.1",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "minimist": "0.0.8"
               }
@@ -23989,7 +24020,8 @@
             "number-is-nan": {
               "version": "1.0.1",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "object-assign": {
               "version": "4.1.1",
@@ -24001,6 +24033,7 @@
               "version": "1.4.0",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "wrappy": "1"
               }
@@ -24086,7 +24119,8 @@
             "safe-buffer": {
               "version": "5.1.1",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "safer-buffer": {
               "version": "2.1.2",
@@ -24122,6 +24156,7 @@
               "version": "1.0.2",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "code-point-at": "^1.0.0",
                 "is-fullwidth-code-point": "^1.0.0",
@@ -24141,6 +24176,7 @@
               "version": "3.0.1",
               "bundled": true,
               "dev": true,
+              "optional": true,
               "requires": {
                 "ansi-regex": "^2.0.0"
               }
@@ -24184,12 +24220,14 @@
             "wrappy": {
               "version": "1.0.2",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             },
             "yallist": {
               "version": "3.0.2",
               "bundled": true,
-              "dev": true
+              "dev": true,
+              "optional": true
             }
           }
         },
diff --git a/package.json b/package.json
index 20169750..036a0a05 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
   "dependencies": {
     "decorate-component-with-props": "^1.0.2",
     "draft-js-plugins-editor": "^2.1.1",
-    "draftjs-conductor": "^0.4.1",
+    "draftjs-conductor": "^0.5.2",
     "draftjs-filters": "^2.2.3"
   },
   "devDependencies": {

From d7ad3f96cb16a08a635cfed0263d459f9eaabdf1 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Tue, 13 Aug 2019 13:48:35 +0100
Subject: [PATCH 07/16] Remove unneeded link suffix

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 1329fc4c..318603cb 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Here are important features worth highlighting:
 
 ## Documentation
 
-- [Getting started](https://www.draftail.org/docs/getting-started.html)
+- [Getting started](https://www.draftail.org/docs/getting-started)
 - [API reference](https://www.draftail.org/docs/api)
 - [User guide](https://www.draftail.org/docs/user-guide)
 - [Getting started with extensions](https://www.draftail.org/docs/getting-started-with-extensions)

From ed9e25b354cc46eeef0e24567832746a9a02ba28 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Tue, 13 Aug 2019 21:27:37 +0100
Subject: [PATCH 08/16] Replace internal conversion API with new methods from
 draftjs-conductor

---
 lib/api/conversion.js            | 35 -----------------
 lib/api/conversion.test.js       | 64 --------------------------------
 lib/components/DraftailEditor.js |  7 ++--
 3 files changed, 4 insertions(+), 102 deletions(-)
 delete mode 100644 lib/api/conversion.js
 delete mode 100644 lib/api/conversion.test.js

diff --git a/lib/api/conversion.js b/lib/api/conversion.js
deleted file mode 100644
index cbed74d0..00000000
--- a/lib/api/conversion.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// @flow
-import { EditorState, convertFromRaw, convertToRaw } from "draft-js";
-import type { RawDraftContentState } from "draft-js/lib/RawDraftContentState";
-
-const EMPTY_CONTENT_STATE = null;
-
-export default {
-  createEditorState(rawContentState: ?RawDraftContentState) {
-    let editorState;
-
-    if (rawContentState) {
-      const contentState = convertFromRaw(rawContentState);
-      editorState = EditorState.createWithContent(contentState);
-    } else {
-      editorState = EditorState.createEmpty();
-    }
-
-    return editorState;
-  },
-
-  serialiseEditorState(editorState: EditorState) {
-    const contentState = editorState.getCurrentContent();
-    const rawContentState = convertToRaw(contentState);
-
-    const isEmpty = rawContentState.blocks.every((block) => {
-      const isEmptyBlock =
-        block.text.trim().length === 0 &&
-        (!block.entityRanges || block.entityRanges.length === 0) &&
-        (!block.inlineStyleRanges || block.inlineStyleRanges.length === 0);
-      return isEmptyBlock;
-    });
-
-    return isEmpty ? EMPTY_CONTENT_STATE : rawContentState;
-  },
-};
diff --git a/lib/api/conversion.test.js b/lib/api/conversion.test.js
deleted file mode 100644
index 7b93c347..00000000
--- a/lib/api/conversion.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { EditorState, convertFromRaw, convertToRaw } from "draft-js";
-import conversion from "./conversion";
-
-const stubContent = {
-  entityMap: {},
-  blocks: [
-    {
-      key: "1dcqo",
-      text: "Hello, World!",
-      type: "unstyled",
-      depth: 0,
-      inlineStyleRanges: [],
-      entityRanges: [],
-      data: {},
-    },
-    {
-      key: "dmtba",
-      text: "This is a title",
-      type: "header-two",
-      depth: 0,
-      inlineStyleRanges: [],
-      entityRanges: [],
-      data: {},
-    },
-  ],
-};
-
-describe("conversion", () => {
-  describe("#createEditorState", () => {
-    it("creates state from real content", () => {
-      const state = conversion.createEditorState(stubContent);
-      const result = convertToRaw(state.getCurrentContent());
-      expect(result.blocks.length).toEqual(2);
-      expect(result.blocks[0].text).toEqual("Hello, World!");
-    });
-  });
-
-  describe("#serialiseEditorState", () => {
-    it("keeps real content", () => {
-      const state = conversion.createEditorState(stubContent);
-      expect(conversion.serialiseEditorState(state)).toEqual(stubContent);
-    });
-
-    it("discards empty content", () => {
-      const state = conversion.createEditorState(null);
-      expect(conversion.serialiseEditorState(state)).toBeNull();
-    });
-
-    it("discards content with only empty text", () => {
-      const editorState = EditorState.createWithContent(
-        convertFromRaw({
-          entityMap: {},
-          blocks: [
-            {
-              key: "a",
-              text: "",
-            },
-          ],
-        }),
-      );
-      expect(conversion.serialiseEditorState(editorState)).toBeNull();
-    });
-  });
-});
diff --git a/lib/components/DraftailEditor.js b/lib/components/DraftailEditor.js
index 92870c57..792243df 100644
--- a/lib/components/DraftailEditor.js
+++ b/lib/components/DraftailEditor.js
@@ -12,6 +12,8 @@ import {
   ListNestingStyles,
   registerCopySource,
   handleDraftEditorPastedText,
+  createEditorStateFromRaw,
+  serialiseEditorStateToRaw,
 } from "draftjs-conductor";
 import decorateComponentWithProps from "decorate-component-with-props";
 
@@ -27,7 +29,6 @@ import {
 
 import DraftUtils from "../api/DraftUtils";
 import behavior from "../api/behavior";
-import conversion from "../api/conversion";
 
 import Toolbar from "./Toolbar";
 import type { ToolbarProps } from "./Toolbar";
@@ -268,7 +269,7 @@ class DraftailEditor extends Component<Props, State> {
     if (editorStateProp !== null) {
       this.getEditorState = this.getEditorStateProp.bind(this);
     } else {
-      this.state.editorState = conversion.createEditorState(rawContentState);
+      this.state.editorState = createEditorStateFromRaw(rawContentState);
       this.getEditorState = this.getEditorStateState.bind(this);
     }
   }
@@ -491,7 +492,7 @@ class DraftailEditor extends Component<Props, State> {
     const editorState = this.getEditorState();
 
     if (onSave) {
-      onSave(conversion.serialiseEditorState(editorState));
+      onSave(serialiseEditorStateToRaw(editorState));
     }
   }
 

From d8a91b662487e7fbde8a0dcb548824d9ed8b9490 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Wed, 14 Aug 2019 16:31:06 +0100
Subject: [PATCH 09/16] =?UTF-8?q?Expose=20draftjs-conductor=20data=20conve?=
 =?UTF-8?q?rsion=20APIs=20through=20Draftail=E2=80=99s=20API?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/index.js | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lib/index.js b/lib/index.js
index 14844b92..2a9670c7 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -8,3 +8,8 @@ export { default as Icon } from "./components/Icon";
 export { default as ToolbarButton } from "./components/ToolbarButton";
 export { default as DraftUtils } from "./api/DraftUtils";
 export { BLOCK_TYPE, ENTITY_TYPE, INLINE_STYLE } from "./api/constants";
+// Expose methods from draftjs-conductor directly for users of Draftail.
+export {
+  createEditorStateFromRaw,
+  serialiseEditorStateToRaw,
+} from "draftjs-conductor";

From c2c072e5308e8fb1c6b3926a8b872a371060510c Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Wed, 14 Aug 2019 16:32:16 +0100
Subject: [PATCH 10/16] Use data conversion APIs in EditorWrapper

---
 examples/components/EditorWrapper.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/examples/components/EditorWrapper.js b/examples/components/EditorWrapper.js
index 0ca4ca56..26a0be76 100644
--- a/examples/components/EditorWrapper.js
+++ b/examples/components/EditorWrapper.js
@@ -1,9 +1,9 @@
 // @flow
 import React, { Component } from "react";
-import { EditorState, convertToRaw } from "draft-js";
+import { EditorState } from "draft-js";
 import type { RawDraftContentState } from "draft-js/lib/RawDraftContentState";
 
-import { DraftailEditor } from "../../lib";
+import { DraftailEditor, serialiseEditorStateToRaw } from "../../lib";
 
 import SentryBoundary from "./SentryBoundary";
 import Highlight from "./Highlight";
@@ -55,7 +55,7 @@ class EditorWrapper extends Component<Props, State> {
   /* :: onChange: (nextState: EditorState) => void; */
   onChange(nextState: EditorState) {
     const { id, onChange } = this.props;
-    const content = convertToRaw(nextState.getCurrentContent());
+    const content = serialiseEditorStateToRaw(nextState);
 
     this.setState(({ saveCount }) => ({ content, saveCount: saveCount + 1 }));
 

From b84375a3e7b62f563d8be5d4b44178d2f140d84f Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Wed, 14 Aug 2019 16:32:32 +0100
Subject: [PATCH 11/16] Remove dead code from getComponentWrapper

---
 .../getComponentWrapper.test.js.snap            |  7 -------
 lib/utils/getComponentWrapper.js                | 17 -----------------
 lib/utils/getComponentWrapper.test.js           | 15 ---------------
 3 files changed, 39 deletions(-)
 delete mode 100644 lib/utils/__snapshots__/getComponentWrapper.test.js.snap
 delete mode 100644 lib/utils/getComponentWrapper.js
 delete mode 100644 lib/utils/getComponentWrapper.test.js

diff --git a/lib/utils/__snapshots__/getComponentWrapper.test.js.snap b/lib/utils/__snapshots__/getComponentWrapper.test.js.snap
deleted file mode 100644
index 2c805447..00000000
--- a/lib/utils/__snapshots__/getComponentWrapper.test.js.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`getComponentWrapper works 1`] = `
-<Wrapped
-  test={true}
-/>
-`;
diff --git a/lib/utils/getComponentWrapper.js b/lib/utils/getComponentWrapper.js
deleted file mode 100644
index 6651981a..00000000
--- a/lib/utils/getComponentWrapper.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// @flow
-import React from "react";
-import type { ComponentType } from "react";
-
-/**
- * Wraps a component to provide it with additional props based on context.
- */
-const getComponentWrapper = (Wrapped: ComponentType<{}>, wrapperProps: {}) => {
-  const Wrapper = (props: {}) => (
-    // flowlint inexact-spread:off
-    <Wrapped {...props} {...wrapperProps} />
-  );
-
-  return Wrapper;
-};
-
-export default getComponentWrapper;
diff --git a/lib/utils/getComponentWrapper.test.js b/lib/utils/getComponentWrapper.test.js
deleted file mode 100644
index 83cabb88..00000000
--- a/lib/utils/getComponentWrapper.test.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from "react";
-import { shallow } from "enzyme";
-
-import getComponentWrapper from "./getComponentWrapper";
-
-describe("getComponentWrapper", () => {
-  it("works", () => {
-    const Wrapped = () => <div />;
-    const Wrapper = getComponentWrapper(Wrapped, {
-      test: true,
-    });
-
-    expect(shallow(<Wrapper />)).toMatchSnapshot();
-  });
-});

From a031487ef04e87208bb39188248af8c7119e0525 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Wed, 14 Aug 2019 18:44:35 +0100
Subject: [PATCH 12/16] Convert otherwise-unused import into Flow-only import

---
 examples/components/EditorWrapper.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/examples/components/EditorWrapper.js b/examples/components/EditorWrapper.js
index 26a0be76..dedf0bf7 100644
--- a/examples/components/EditorWrapper.js
+++ b/examples/components/EditorWrapper.js
@@ -1,6 +1,6 @@
 // @flow
 import React, { Component } from "react";
-import { EditorState } from "draft-js";
+/* :: import { EditorState } from "draft-js"; */
 import type { RawDraftContentState } from "draft-js/lib/RawDraftContentState";
 
 import { DraftailEditor, serialiseEditorStateToRaw } from "../../lib";

From 222cb055b77551460d38a089c675a5a70ad15037 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Wed, 14 Aug 2019 22:55:35 +0100
Subject: [PATCH 13/16] Fix EditorWrapper not loading preset content

---
 examples/components/EditorWrapper.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/examples/components/EditorWrapper.js b/examples/components/EditorWrapper.js
index dedf0bf7..11ae2266 100644
--- a/examples/components/EditorWrapper.js
+++ b/examples/components/EditorWrapper.js
@@ -85,7 +85,7 @@ class EditorWrapper extends Component<Props, State> {
     } else {
       const storedContent = sessionStorage.getItem(`${id}:content`) || "null";
       initialContent =
-        rawContentState || storedContent ? JSON.parse(storedContent) : null;
+        rawContentState || (storedContent ? JSON.parse(storedContent) : null);
       dataProps.rawContentState = initialContent;
       dataProps.onSave = this.onSave;
     }

From 562ea9251b75c14c83e00d198a67f6b6de4f4880 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Wed, 14 Aug 2019 22:56:58 +0100
Subject: [PATCH 14/16] Add CHANGELOG updates for controlled component feature

---
 CHANGELOG.md                     | 1 +
 lib/components/DraftailEditor.js | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8bc9fef..6bfdba39 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 ### Added
 
 - Add ability to disable the editor on demand with the [`readOnly`](https://www.draftail.org/docs/next/api#draftaileditor) prop, matching behavior of Draft.js. [#201](https://github.com/springload/draftail/issues/201), [#206](https://github.com/springload/draftail/pull/206), thanks to [@SpearThruster](https://github.com/SpearThruster).
+- Add ability to use the editor as a controlled component, like vanilla Draft.js editors, with [`editorState` and `onChange`](https://www.draftail.org/docs/next/api#editorstate-and-onchange) props. Have a look at the [controlled component documentation](https://www.draftail.org/docs/next/controlled-component) for further details. [#180](https://github.com/springload/draftail/issues/180), [#207](https://github.com/springload/draftail/pull/207).
 
 ### Fixed
 
diff --git a/lib/components/DraftailEditor.js b/lib/components/DraftailEditor.js
index 792243df..78443a3f 100644
--- a/lib/components/DraftailEditor.js
+++ b/lib/components/DraftailEditor.js
@@ -124,9 +124,9 @@ const defaultProps = {
   rawContentState: null,
   // Called when changes occured. Use this to persist editor content.
   onSave: null,
-  // Content of the editor, when using the editor as a controlled component. Incompatible with `rawContentState`.
+  // Content of the editor, when using the editor as a controlled component. Incompatible with `rawContentState` and `onSave`.
   editorState: null,
-  // Called whenever the editor state is updated. Use this to store the content of a controlled editor.
+  // Called whenever the editor state is updated. Use this to manage the content of a controlled editor. Incompatible with `rawContentState` and `onSave`.
   onChange: null,
   // Called when the editor receives focus.
   onFocus: null,

From ca292f792ec29781caadd52a5c5ae464d5629962 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Wed, 14 Aug 2019 23:07:11 +0100
Subject: [PATCH 15/16] Update draftjs-conductor to latest

---
 package-lock.json | 6 +++---
 package.json      | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 3445b56f..c1c93ba0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10123,9 +10123,9 @@
       }
     },
     "draftjs-conductor": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/draftjs-conductor/-/draftjs-conductor-0.5.2.tgz",
-      "integrity": "sha512-g7j7Kjxr5wIAXnS6bD/yDf3gq7FpfiHmnp5RFgjw+54GGN6EIO1ABh/OCw9LAU/xTCJ9dHIGYLHStR0KWUic+Q=="
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/draftjs-conductor/-/draftjs-conductor-1.0.0.tgz",
+      "integrity": "sha512-pz5MIpS2aH6fgo2jrHo0Rt+rxqRgqnbrxEaaTiFLW4Dg/0U0eZhyxZPt6pByoS20Q7jCYOpolGpszKodNVi+TQ=="
     },
     "draftjs-filters": {
       "version": "2.2.3",
diff --git a/package.json b/package.json
index 036a0a05..58a13be2 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
   "dependencies": {
     "decorate-component-with-props": "^1.0.2",
     "draft-js-plugins-editor": "^2.1.1",
-    "draftjs-conductor": "^0.5.2",
+    "draftjs-conductor": "^1.0.0",
     "draftjs-filters": "^2.2.3"
   },
   "devDependencies": {

From 5d66d08ab12d0be3a64ed826546e64d6a55cc532 Mon Sep 17 00:00:00 2001
From: Thibaud Colas <thibaudcolas@gmail.com>
Date: Wed, 14 Aug 2019 23:27:29 +0100
Subject: [PATCH 16/16] Cleanup unnecessary prop rename

---
 lib/components/DraftailEditor.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/components/DraftailEditor.js b/lib/components/DraftailEditor.js
index 78443a3f..63bea050 100644
--- a/lib/components/DraftailEditor.js
+++ b/lib/components/DraftailEditor.js
@@ -257,7 +257,7 @@ class DraftailEditor extends Component<Props, State> {
 
     this.renderSource = this.renderSource.bind(this);
 
-    const { editorState: editorStateProp, rawContentState } = props;
+    const { editorState, rawContentState } = props;
 
     this.state = {
       readOnlyState: false,
@@ -265,10 +265,10 @@ class DraftailEditor extends Component<Props, State> {
       sourceOptions: null,
     };
 
-    // If editorState is not used as a prop, create it in local state from rawContentState.
-    if (editorStateProp !== null) {
+    if (editorState !== null) {
       this.getEditorState = this.getEditorStateProp.bind(this);
     } else {
+      // If editorState is not used as a prop, create it in local state from rawContentState.
       this.state.editorState = createEditorStateFromRaw(rawContentState);
       this.getEditorState = this.getEditorStateState.bind(this);
     }
