You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@couchdb.apache.org by GitBox <gi...@apache.org> on 2018/10/16 13:25:06 UTC

[GitHub] Antonio-Maranhao closed pull request #1132: Doc Editor Redux refactoring

Antonio-Maranhao closed pull request #1132: Doc Editor Redux refactoring
URL: https://github.com/apache/couchdb-fauxton/pull/1132
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index c5f78261e..147eb84f8 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -20,6 +20,7 @@ import mangoReducers from "./mango/mango.reducers";
 import sidebarReducers from "./sidebar/reducers";
 import partitionKeyReducers from "./partition-key/reducers";
 import revisionBrowserReducers from './rev-browser/reducers';
+import docEditorReducers from './doc-editor/reducers';
 import changesReducers from './changes/reducers';
 import "./assets/less/documents.less";
 
@@ -29,6 +30,7 @@ FauxtonAPI.addReducers({
   sidebar: sidebarReducers,
   revisionBrowser: revisionBrowserReducers,
   partitionKey: partitionKeyReducers,
+  docEditor: docEditorReducers,
   changes: changesReducers,
   designDocInfo: designDocInfoReducers
 });
diff --git a/app/addons/documents/doc-editor/__tests__/doc-editor.actions.test.js b/app/addons/documents/doc-editor/__tests__/doc-editor.actions.test.js
index 603a0e61a..425a27f99 100644
--- a/app/addons/documents/doc-editor/__tests__/doc-editor.actions.test.js
+++ b/app/addons/documents/doc-editor/__tests__/doc-editor.actions.test.js
@@ -56,8 +56,9 @@ describe('DocEditorActions', () => {
         }
       ]
     };
+    const mockDispatch = () => {};
 
-    Actions.uploadAttachment(params);
+    Actions.uploadAttachment(params)(mockDispatch);
     sinon.assert.calledWithExactly(
       fakeOpen,
       'PUT',
diff --git a/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js b/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
index c247e91c3..35505f4fd 100644
--- a/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
+++ b/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
@@ -10,16 +10,21 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import FauxtonAPI from "../../../../core/api";
-import React from "react";
-import ReactDOM from "react-dom";
-import Documents from "../../resources";
-import Components from "../components";
-import Actions from "../actions";
-import ActionTypes from "../actiontypes";
-import Databases from "../../../databases/base";
-import utils from "../../../../../test/mocha/testUtils";
-import {mount} from 'enzyme';
+import FauxtonAPI from '../../../../core/api';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Documents from '../../resources';
+import AttachmentsPanelButton from '../components/AttachmentsPanelButton';
+import DocEditorScreen from '../components/DocEditorScreen';
+import DocEditorContainer from '../components/DocEditorContainer';
+import Databases from '../../../databases/base';
+import utils from '../../../../../test/mocha/testUtils';
+import { mount } from 'enzyme';
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
+import { createStore, applyMiddleware, combineReducers } from 'redux';
+import docEditorReducer from '../reducers';
+
 import '../../base';
 
 FauxtonAPI.router = new FauxtonAPI.Router([]);
@@ -54,26 +59,52 @@ const docWithAttachmentsJSON = {
 };
 
 const database = new Databases.Model({ id: 'a/special?db' });
+const defaultProps = {
+  isLoading: true,
+  isNewDoc: true,
+  database: database,
+  doc: new Documents.NewDoc(null, { database: database }),
+  conflictCount: 0,
+  saveDoc: () => {},
+
+  isCloneDocModalVisible: false,
+  showCloneDocModal: () => {},
+  hideCloneDocModal: () => {},
+  cloneDoc: () => {},
+
+  isDeleteDocModalVisible: false,
+  showDeleteDocModal: () => {},
+  hideDeleteDocModal: () => {},
+  deleteDoc: () => {},
+
+  isUploadModalVisible: false,
+  uploadInProgress: false,
+  uploadPercentage: 0,
+  uploadErrorMessage: '',
+  numFilesUploaded: 0,
+  showUploadModal: () => {},
+  hideUploadModal: () => {},
+  cancelUpload: () => {},
+  resetUploadModal: () => {},
+  uploadAttachment: () => {}
+};
 
+describe('DocEditorScreen', () => {
 
-describe('DocEditorController', () => {
   it('loading indicator appears on load', () => {
-    const el = mount(<Components.DocEditorController />);
+    const el = mount(<DocEditorScreen {...defaultProps} />);
     assert.equal(el.find('.loading-lines').length, 1);
   });
 
   it('new docs do not show the button row', () => {
-    const el = mount(<Components.DocEditorController isNewDoc={true} database={database} />);
-
     const doc = new Documents.Doc(docJSON, { database: database });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.DOC_LOADED,
-      options: {
-        doc: doc
-      }
-    });
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={true}
+      database={database}
+      doc={doc} />);
 
-    el.update();
     assert.equal(el.find('.loading-lines').length, 0);
     assert.equal(el.find('.icon-circle-arrow-up').length, 0);
     assert.equal(el.find('.icon-repeat').length, 0);
@@ -81,58 +112,49 @@ describe('DocEditorController', () => {
   });
 
   it('view attachments button does not appear with no attachments', () => {
-    const el = mount(<Components.DocEditorController database={database} />);
-
     const doc = new Documents.Doc(docJSON, { database: database });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.DOC_LOADED,
-      options: {
-        doc: doc
-      }
-    });
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={false}
+      database={database}
+      doc={doc} />);
+
     assert.equal(el.find('.view-attachments-section').length, 0);
   });
 
   it('view attachments button shows up when the doc has attachments', () => {
-    const el = mount(<Components.DocEditorController database={database} />);
-
     const doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.DOC_LOADED,
-      options: {
-        doc: doc
-      }
-    });
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={false}
+      database={database}
+      doc={doc} />);
 
-    el.update();
     assert.equal(el.find('.view-attachments-section').length, 1);
   });
 
   it('view attachments dropdown contains right number of docs', () => {
-    const el = mount(<Components.DocEditorController database={database} />);
-
     const doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.DOC_LOADED,
-      options: {
-        doc: doc
-      }
-    });
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={false}
+      database={database}
+      doc={doc} />);
+
     assert.equal(el.find('.view-attachments-section .dropdown-menu li').length, 2);
   });
 
   it('view attachments dropdown contains correct urls', () => {
-    const el = mount(
-      <Components.DocEditorController database={database} />
-    );
-
     const doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.DOC_LOADED,
-      options: {
-        doc: doc
-      }
-    });
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={false}
+      database={database}
+      doc={doc} />);
 
     const $attachmentNode = el.find('.view-attachments-section .dropdown-menu li');
     const attachmentURLactual = $attachmentNode.find('a').first().prop('href');
@@ -140,40 +162,54 @@ describe('DocEditorController', () => {
     assert.equal(attachmentURLactual, './a%2Fspecial%3Fdb/_design%2Ftest%23doc/one%252F.png');
   });
 
-  it.skip('setting deleteDocModal=true in store shows modal', () => {
-    mount(<Components.DocEditorController database={database} />);
-    const doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.DOC_LOADED,
-      options: {
-        doc: doc
-      }
-    });
-
-    // uber-kludgy, but the delete doc modal is a generic dialog used multiple times, so this test first checks
-    // no modal is open, then confirms the open modal contains the delete dialog message
-    assert.equal($('body').find('.confirmation-modal').length, 0);
-
-    Actions.showDeleteDocModal();
+});
 
-    const modalContent = $('body').find('.confirmation-modal .modal-body p')[0];
-    assert.ok(/Are you sure you want to delete this document\?/.test(modalContent.innerHTML));
+describe('DocEditorContainer', () => {
+  const middlewares = [thunk];
+  const store = createStore(
+    combineReducers({ docEditor: docEditorReducer }),
+    applyMiddleware(...middlewares)
+  );
+
+  it('clicking Delete button shows the confirmation modal', () => {
+    const wrapper = mount(
+      <Provider store={store}>
+        <DocEditorContainer
+          isNewDoc={false}
+          database={database} />
+      </Provider>
+    );
+    assert.equal(wrapper.find(DocEditorScreen).prop('isDeleteDocModalVisible'), false);
+    wrapper.find('button[title="Delete"]').simulate('click');
+    assert.equal(wrapper.find(DocEditorScreen).prop('isDeleteDocModalVisible'), true);
   });
 
-  it.skip('setting uploadDocModal=true in store shows modal', () => {
-    mount(<Components.DocEditorController database={database} />);
-    const doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-    FauxtonAPI.dispatch({
-      type: ActionTypes.DOC_LOADED,
-      options: {
-        doc: doc
-      }
-    });
+  it('clicking Upload button shows the upload dialog', () => {
+    const wrapper = mount(
+      <Provider store={store}>
+        <DocEditorContainer
+          isNewDoc={false}
+          database={database} />
+      </Provider>
+    );
+    assert.equal(wrapper.find(DocEditorScreen).prop('isUploadModalVisible'), false);
+    wrapper.find('button[title="Upload Attachment"]').simulate('click');
+    assert.equal(wrapper.find(DocEditorScreen).prop('isUploadModalVisible'), true);
+  });
 
-    assert.equal($('body').find('.upload-file-modal').length, 0);
-    Actions.showUploadModal();
-    assert.notEqual($('body').find('.upload-file-modal').length, 0);
+  it('clicking Clone button shows the clone doc dialog', () => {
+    const wrapper = mount(
+      <Provider store={store}>
+        <DocEditorContainer
+          isNewDoc={false}
+          database={database} />
+      </Provider>
+    );
+    assert.equal(wrapper.find(DocEditorScreen).prop('isCloneDocModalVisible'), false);
+    wrapper.find('button[title="Clone Document"]').simulate('click');
+    assert.equal(wrapper.find(DocEditorScreen).prop('isCloneDocModalVisible'), true);
   });
+
 });
 
 
@@ -185,12 +221,12 @@ describe("AttachmentsPanelButton", () => {
   });
 
   it('does not show up when loading', () => {
-    const el = mount(<Components.AttachmentsPanelButton isLoading={true} doc={doc} />);
+    const el = mount(<AttachmentsPanelButton isLoading={true} doc={doc} />);
     assert.equal(el.find('.panel-button').length, 0);
   });
 
   it('shows up after loading', () => {
-    const el = mount(<Components.AttachmentsPanelButton isLoading={false} doc={doc} />);
+    const el = mount(<AttachmentsPanelButton isLoading={false} doc={doc} />);
     assert.equal(el.find('button.panel-button').length, 1);
   });
 });
@@ -211,9 +247,12 @@ describe("Custom Extension Buttons", () => {
 
     FauxtonAPI.registerExtension('DocEditor:icons', CustomButton);
 
-    const el = mount(<Components.DocEditorController database={database} />);
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={false}
+      database={database} />);
     assert.isTrue(/Oh\sno\sshe\sdi'n't!/.test(el.html()));
-
     // confirm the database name was also included
     assert.equal(el.find("#testDatabaseName").text(), database.id);
   });
diff --git a/app/addons/documents/doc-editor/__tests__/doc-editor.reducers.test.js b/app/addons/documents/doc-editor/__tests__/doc-editor.reducers.test.js
new file mode 100644
index 000000000..093abe56a
--- /dev/null
+++ b/app/addons/documents/doc-editor/__tests__/doc-editor.reducers.test.js
@@ -0,0 +1,70 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import FauxtonAPI from "../../../../core/api";
+import utils from "../../../../../test/mocha/testUtils";
+import Documents from "../../resources";
+import ActionTypes from "../actiontypes";
+import reducer from "../reducers";
+
+FauxtonAPI.router = new FauxtonAPI.Router([]);
+
+const assert = utils.assert;
+const doc = new Documents.Doc({id: 'foo'}, {database: 'bar'});
+
+describe('DocEditor Reducer', function () {
+
+  it('defines sensible defaults', function () {
+    const newState = reducer(undefined, { type: 'do_nothing'});
+
+    assert.equal(newState.isLoading, true);
+    assert.equal(newState.cloneDocModalVisible, false);
+    assert.equal(newState.deleteDocModalVisible, false);
+    assert.equal(newState.uploadModalVisible, false);
+    assert.equal(newState.numFilesUploaded, 0);
+    assert.equal(newState.uploadInProgress, false);
+    assert.equal(newState.uploadPercentage, 0);
+  });
+
+  it('marks loading as complete after doc is loaded', function () {
+    const newState = reducer(undefined, {
+      type: ActionTypes.DOC_LOADED,
+      options: { doc: doc }
+    });
+    assert.equal(newState.isLoading, false);
+  });
+
+  it('showCloneDocModal / hideCloneDocModal', function () {
+    const newStateShow = reducer(undefined, { type: ActionTypes.SHOW_CLONE_DOC_MODAL });
+    assert.equal(newStateShow.cloneDocModalVisible, true);
+
+    const newStateHide = reducer(undefined, { type: ActionTypes.HIDE_CLONE_DOC_MODAL });
+    assert.equal(newStateHide.cloneDocModalVisible, false);
+  });
+
+  it('showDeleteDocModal / hideDeleteDocModal', function () {
+    const newStateShow = reducer(undefined, { type: ActionTypes.SHOW_DELETE_DOC_CONFIRMATION_MODAL });
+    assert.equal(newStateShow.deleteDocModalVisible, true);
+
+    const newStateHide = reducer(undefined, { type: ActionTypes.HIDE_DELETE_DOC_CONFIRMATION_MODAL });
+    assert.equal(newStateHide.deleteDocModalVisible, false);
+  });
+
+  it('showUploadModal / hideUploadModal', function () {
+    const newStateShow = reducer(undefined, { type: ActionTypes.SHOW_UPLOAD_MODAL });
+    assert.equal(newStateShow.uploadModalVisible, true);
+
+    const newStateHide = reducer(undefined, { type: ActionTypes.HIDE_UPLOAD_MODAL });
+    assert.equal(newStateHide.uploadModalVisible, false);
+  });
+
+});
diff --git a/app/addons/documents/doc-editor/__tests__/doc-editor.stores.test.js b/app/addons/documents/doc-editor/__tests__/doc-editor.stores.test.js
deleted file mode 100644
index ac9c4bbfd..000000000
--- a/app/addons/documents/doc-editor/__tests__/doc-editor.stores.test.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../../core/api";
-import Stores from "../stores";
-import Documents from "../../resources";
-import utils from "../../../../../test/mocha/testUtils";
-FauxtonAPI.router = new FauxtonAPI.Router([]);
-
-const assert = utils.assert;
-const store = Stores.docEditorStore;
-
-const doc = new Documents.Doc({id: 'foo'}, {database: 'bar'});
-
-describe('DocEditorStore', function () {
-  afterEach(function () {
-    store.reset();
-  });
-
-  it('defines sensible defaults', function () {
-    assert.equal(store.isLoading(), true);
-    assert.equal(store.isCloneDocModalVisible(), false);
-    assert.equal(store.isDeleteDocModalVisible(), false);
-    assert.equal(store.isUploadModalVisible(), false);
-    assert.equal(store.getNumFilesUploaded(), 0);
-    assert.equal(store.isUploadInProgress(), false);
-    assert.equal(store.getUploadLoadPercentage(), 0);
-  });
-
-  it('docLoaded() marks loading as complete', function () {
-    store.docLoaded({ doc: doc });
-    assert.equal(store.isLoading(), false);
-  });
-
-  it('showCloneDocModal / hideCloneDocModal', function () {
-    store.showCloneDocModal();
-    assert.equal(store.isCloneDocModalVisible(), true);
-    store.hideCloneDocModal();
-    assert.equal(store.isCloneDocModalVisible(), false);
-  });
-
-  it('showDeleteDocModal / hideCloneDocModal', function () {
-    store.showDeleteDocModal();
-    assert.equal(store.isDeleteDocModalVisible(), true);
-    store.hideDeleteDocModal();
-    assert.equal(store.isDeleteDocModalVisible(), false);
-  });
-
-  it('showCloneDocModal / hideCloneDocModal', function () {
-    store.showUploadModal();
-    assert.equal(store.isUploadModalVisible(), true);
-    store.hideUploadModal();
-    assert.equal(store.isUploadModalVisible(), false);
-  });
-
-  it('reset() resets all values', function () {
-    store.docLoaded({ doc: doc });
-    store.showCloneDocModal();
-    store.showDeleteDocModal();
-    store.showUploadModal();
-
-    store.reset();
-    assert.equal(store.isLoading(), true);
-    assert.equal(store.isCloneDocModalVisible(), false);
-    assert.equal(store.isDeleteDocModalVisible(), false);
-    assert.equal(store.isUploadModalVisible(), false);
-  });
-
-});
diff --git a/app/addons/documents/doc-editor/actions.js b/app/addons/documents/doc-editor/actions.js
index 6d0abc121..12120513d 100644
--- a/app/addons/documents/doc-editor/actions.js
+++ b/app/addons/documents/doc-editor/actions.js
@@ -12,20 +12,24 @@
 
 /* global FormData */
 
-import FauxtonAPI from "../../../core/api";
-import { deleteRequest } from "../../../core/ajax";
-import ActionTypes from "./actiontypes";
+import FauxtonAPI from '../../../core/api';
+import { deleteRequest } from '../../../core/ajax';
+import ActionTypes from './actiontypes';
 
 var currentUploadHttpRequest;
 
-function initDocEditor (params) {
-  var doc = params.doc;
+const dispatchInitDocEditor = (params) => {
+  FauxtonAPI.reduxDispatch(initDocEditor(params));
+};
+
+const initDocEditor = (params) => (dispatch) => {
+  const doc = params.doc;
 
   // ensure a clean slate
-  FauxtonAPI.dispatch({ type: ActionTypes.RESET_DOC });
+  dispatch({ type: ActionTypes.RESET_DOC });
 
   doc.fetch().then(function () {
-    FauxtonAPI.dispatch({
+    dispatch({
       type: ActionTypes.DOC_LOADED,
       options: {
         doc: doc
@@ -42,9 +46,9 @@ function initDocEditor (params) {
 
     FauxtonAPI.navigate(FauxtonAPI.urls('allDocs', 'app', params.database.id, ''));
   });
-}
+};
 
-function saveDoc (doc, isValidDoc, onSave) {
+const saveDoc = (doc, isValidDoc, onSave) => {
   if (isValidDoc) {
     FauxtonAPI.addNotification({
       msg: 'Saving document.',
@@ -69,17 +73,17 @@ function saveDoc (doc, isValidDoc, onSave) {
   } else {
     errorNotification('Please fix the JSON errors and try saving again.');
   }
-}
+};
 
-function showDeleteDocModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.SHOW_DELETE_DOC_CONFIRMATION_MODAL });
-}
+const showDeleteDocModal = () => (dispatch) => {
+  dispatch({ type: ActionTypes.SHOW_DELETE_DOC_CONFIRMATION_MODAL });
+};
 
-function hideDeleteDocModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.HIDE_DELETE_DOC_CONFIRMATION_MODAL });
-}
+const hideDeleteDocModal = () => (dispatch) => {
+  dispatch({ type: ActionTypes.HIDE_DELETE_DOC_CONFIRMATION_MODAL });
+};
 
-function deleteDoc (doc) {
+const deleteDoc = (doc) => {
   const databaseName = doc.database.safeID();
   const query = '?rev=' + doc.get('_rev');
   const url = FauxtonAPI.urls('document', 'server', databaseName, doc.safeID(), query);
@@ -100,22 +104,22 @@ function deleteDoc (doc) {
       clear: true
     });
   });
-}
-
-function showCloneDocModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.SHOW_CLONE_DOC_MODAL });
-}
+};
 
-function hideCloneDocModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.HIDE_CLONE_DOC_MODAL });
-}
+const showCloneDocModal = () => (dispatch) => {
+  dispatch({ type: ActionTypes.SHOW_CLONE_DOC_MODAL });
+};
 
-function cloneDoc (database, doc, newId) {
+const hideCloneDocModal = () => (dispatch) => {
+  dispatch({ type: ActionTypes.HIDE_CLONE_DOC_MODAL });
+};
 
+const cloneDoc = (database, doc, newId) => {
   hideCloneDocModal();
 
   doc.copy(newId).then(() => {
-    FauxtonAPI.navigate('/database/' + database.safeID() + '/' + encodeURIComponent(newId), { trigger: true });
+    const url = FauxtonAPI.urls('document', 'app', database.safeID(), encodeURIComponent(newId));
+    FauxtonAPI.navigate(url, { trigger: true });
 
     FauxtonAPI.addNotification({
       msg: 'Document has been duplicated.'
@@ -128,20 +132,19 @@ function cloneDoc (database, doc, newId) {
       type: 'error'
     });
   });
+};
 
-}
-
-function showUploadModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.SHOW_UPLOAD_MODAL });
-}
+const showUploadModal = () => (dispatch) => {
+  dispatch({ type: ActionTypes.SHOW_UPLOAD_MODAL });
+};
 
-function hideUploadModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.HIDE_UPLOAD_MODAL });
-}
+const hideUploadModal = () => (dispatch) => {
+  dispatch({ type: ActionTypes.HIDE_UPLOAD_MODAL });
+};
 
-function uploadAttachment (params) {
+const uploadAttachment = (params) => (dispatch) => {
   if (params.files.length === 0) {
-    FauxtonAPI.dispatch({
+    dispatch({
       type: ActionTypes.FILE_UPLOAD_ERROR,
       options: {
         error: 'Please select a file to be uploaded.'
@@ -149,7 +152,7 @@ function uploadAttachment (params) {
     });
     return;
   }
-  FauxtonAPI.dispatch({ type: ActionTypes.START_FILE_UPLOAD });
+  dispatch({ type: ActionTypes.START_FILE_UPLOAD });
 
   const query = '?rev=' + params.rev;
   const db = params.doc.getDatabase().safeID();
@@ -160,7 +163,7 @@ function uploadAttachment (params) {
   const onProgress = (evt) => {
     if (evt.lengthComputable) {
       const percentComplete = evt.loaded / evt.total * 100;
-      FauxtonAPI.dispatch({
+      dispatch({
         type: ActionTypes.SET_FILE_UPLOAD_PERCENTAGE,
         options: {
           percent: percentComplete
@@ -170,20 +173,20 @@ function uploadAttachment (params) {
   };
   const onSuccess = (doc) => {
     // re-initialize the document editor. Only announce it's been updated when
-    initDocEditor({
+    dispatch(initDocEditor({
       doc: doc,
       onLoaded: () => {
-        FauxtonAPI.dispatch({ type: ActionTypes.FILE_UPLOAD_SUCCESS });
+        dispatch({ type: ActionTypes.FILE_UPLOAD_SUCCESS });
         FauxtonAPI.addNotification({
           msg: 'Document saved successfully.',
           type: 'success',
           clear: true
         });
       }
-    });
+    }));
   };
   const onError = (msg) => {
-    FauxtonAPI.dispatch({
+    dispatch({
       type: ActionTypes.FILE_UPLOAD_ERROR,
       options: {
         error: msg
@@ -225,17 +228,17 @@ function uploadAttachment (params) {
   httpRequest.setRequestHeader('Content-Type', file.type || `application/octet-stream`);
   httpRequest.setRequestHeader('Accept', 'application/json');
   httpRequest.send(file);
-}
+};
 
-function cancelUpload () {
+const cancelUpload = () => {
   if (currentUploadHttpRequest) {
     currentUploadHttpRequest.abort();
   }
-}
+};
 
-function resetUploadModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.RESET_UPLOAD_MODAL });
-}
+const resetUploadModal = () => (dispatch) => {
+  dispatch({ type: ActionTypes.RESET_UPLOAD_MODAL });
+};
 
 
 // helpers
@@ -249,23 +252,24 @@ function errorNotification (msg) {
 }
 
 export default {
-  initDocEditor: initDocEditor,
-  saveDoc: saveDoc,
+  dispatchInitDocEditor,
+  initDocEditor,
+  saveDoc,
 
   // clone doc
-  showCloneDocModal: showCloneDocModal,
-  hideCloneDocModal: hideCloneDocModal,
-  cloneDoc: cloneDoc,
+  showCloneDocModal,
+  hideCloneDocModal,
+  cloneDoc,
 
   // delete doc
-  showDeleteDocModal: showDeleteDocModal,
-  hideDeleteDocModal: hideDeleteDocModal,
-  deleteDoc: deleteDoc,
+  showDeleteDocModal,
+  hideDeleteDocModal,
+  deleteDoc,
 
   // upload modal
-  showUploadModal: showUploadModal,
-  hideUploadModal: hideUploadModal,
-  uploadAttachment: uploadAttachment,
-  cancelUpload: cancelUpload,
-  resetUploadModal: resetUploadModal
+  showUploadModal,
+  hideUploadModal,
+  uploadAttachment,
+  cancelUpload,
+  resetUploadModal
 };
diff --git a/app/addons/documents/doc-editor/components.js b/app/addons/documents/doc-editor/components.js
deleted file mode 100644
index 542ba8e84..000000000
--- a/app/addons/documents/doc-editor/components.js
+++ /dev/null
@@ -1,453 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../core/api";
-import PropTypes from 'prop-types';
-import React from "react";
-import { Dropdown, MenuItem } from "react-bootstrap";
-import ReactDOM from "react-dom";
-import Actions from "./actions";
-import Stores from "./stores";
-import FauxtonComponents from "../../fauxton/components";
-import GeneralComponents from "../../components/react-components";
-import { Modal } from "react-bootstrap";
-import Helpers from "../../../helpers";
-
-var store = Stores.docEditorStore;
-
-class DocEditorController extends React.Component {
-  static defaultProps = {
-    database: {},
-    isNewDoc: false
-  };
-
-  getStoreState = () => {
-    return {
-      isLoading: store.isLoading(),
-      doc: store.getDoc(),
-      cloneDocModalVisible: store.isCloneDocModalVisible(),
-      uploadModalVisible: store.isUploadModalVisible(),
-      deleteDocModalVisible: store.isDeleteDocModalVisible(),
-      numFilesUploaded: store.getNumFilesUploaded(),
-      conflictCount: store.getDocConflictCount()
-    };
-  };
-
-  getCodeEditor = () => {
-    if (this.state.isLoading) {
-      return (<GeneralComponents.LoadLines />);
-    }
-
-    var code = JSON.stringify(this.state.doc.attributes, null, '  ');
-    var editorCommands = [{
-      name: 'save',
-      bindKey: { win: 'Ctrl-S', mac: 'Ctrl-S' },
-      exec: this.saveDoc
-    }];
-
-    return (
-      <GeneralComponents.CodeEditor
-        id="doc-editor"
-        ref={node => this.docEditor = node}
-        defaultCode={code}
-        mode="json"
-        autoFocus={true}
-        editorCommands={editorCommands}
-        notifyUnsavedChanges={true}
-        stringEditModalEnabled={true} />
-    );
-  };
-
-  componentDidMount() {
-    store.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    store.off('change', this.onChange);
-  }
-
-  UNSAFE_componentWillUpdate(nextProps, nextState) {
-    // Update the editor whenever a file is uploaded, a doc is cloned, or a new doc is loaded
-    if (this.state.numFilesUploaded !== nextState.numFilesUploaded ||
-        this.state.doc && this.state.doc.hasChanged() ||
-        (this.state.doc && nextState.doc && this.state.doc.id !== nextState.doc.id)) {
-      this.getEditor().setValue(JSON.stringify(nextState.doc.attributes, null, '  '));
-      this.onSaveComplete();
-    }
-  }
-
-  onChange = () => {
-    this.setState(this.getStoreState());
-  };
-
-  saveDoc = () => {
-    Actions.saveDoc(this.state.doc, this.checkDocIsValid(), this.onSaveComplete);
-  };
-
-  onSaveComplete = () => {
-    this.getEditor().clearChanges();
-  };
-
-  hideDeleteDocModal = () => {
-    Actions.hideDeleteDocModal();
-  };
-
-  deleteDoc = () => {
-    Actions.hideDeleteDocModal();
-    Actions.deleteDoc(this.state.doc);
-  };
-
-  getEditor = () => {
-    return (this.docEditor) ? this.docEditor.getEditor() : null;
-  };
-
-  checkDocIsValid = () => {
-    if (this.getEditor().hasErrors()) {
-      return false;
-    }
-    var json = JSON.parse(this.getEditor().getValue());
-    this.state.doc.clear().set(json, { validate: true });
-
-    return !this.state.doc.validationError;
-  };
-
-  clearChanges = () => {
-    this.docEditor.clearChanges();
-  };
-
-  getExtensionIcons = () => {
-    var extensions = FauxtonAPI.getExtensions('DocEditor:icons');
-    return _.map(extensions, (Extension, i) => {
-      return (<Extension doc={this.state.doc} key={i} database={this.props.database} />);
-    });
-  };
-
-  getButtonRow = () => {
-    if (this.props.isNewDoc) {
-      return false;
-    }
-    return (
-      <div>
-        <AttachmentsPanelButton doc={this.state.doc} isLoading={this.state.isLoading} />
-        <div className="doc-editor-extension-icons">{this.getExtensionIcons()}</div>
-
-        {this.state.conflictCount ? <PanelButton
-          title={`Conflicts (${this.state.conflictCount})`}
-          iconClass="icon-columns"
-          className="conflicts"
-          onClick={() => { FauxtonAPI.navigate(FauxtonAPI.urls('revision-browser', 'app', this.props.database.safeID(), this.state.doc.id));}}/> : null}
-
-        <PanelButton className="upload" title="Upload Attachment" iconClass="icon-circle-arrow-up" onClick={Actions.showUploadModal} />
-        <PanelButton title="Clone Document" iconClass="icon-repeat" onClick={Actions.showCloneDocModal} />
-        <PanelButton title="Delete" iconClass="icon-trash" onClick={Actions.showDeleteDocModal} />
-      </div>
-    );
-  };
-
-  state = this.getStoreState();
-
-  render() {
-    var saveButtonLabel = (this.props.isNewDoc) ? 'Create Document' : 'Save Changes';
-    let endpoint = FauxtonAPI.urls('allDocs', 'app', FauxtonAPI.url.encode(this.props.database.id));
-    return (
-      <div>
-        <div id="doc-editor-actions-panel">
-          <div className="doc-actions-left">
-            <button className="save-doc btn btn-primary save" type="button" onClick={this.saveDoc}>
-              <i className="icon fonticon-ok-circled"></i> {saveButtonLabel}
-            </button>
-            <div>
-              <a href={`#/${endpoint}`} className="js-back cancel-button">Cancel</a>
-            </div>
-          </div>
-          <div className="alignRight">
-            {this.getButtonRow()}
-          </div>
-        </div>
-
-        <div className="code-region">
-          <div className="bgEditorGutter"></div>
-          <div id="editor-container" className="doc-code">{this.getCodeEditor()}</div>
-
-        </div>
-
-        <UploadModal
-          ref={node => this.uploadModal = node}
-          visible={this.state.uploadModalVisible}
-          doc={this.state.doc} />
-        <CloneDocModal
-          doc={this.state.doc}
-          database={this.props.database}
-          visible={this.state.cloneDocModalVisible}
-          onSubmit={this.clearChanges} />
-        <FauxtonComponents.ConfirmationModal
-          title="Confirm Deletion"
-          visible={this.state.deleteDocModalVisible}
-          text="Are you sure you want to delete this document?"
-          onClose={this.hideDeleteDocModal}
-          onSubmit={this.deleteDoc}
-          successButtonLabel="Delete Document" />
-      </div>
-    );
-  }
-}
-
-class AttachmentsPanelButton extends React.Component {
-  static propTypes = {
-    isLoading: PropTypes.bool.isRequired,
-    doc: PropTypes.object
-  };
-
-  static defaultProps = {
-    isLoading: true,
-    doc: {}
-  };
-
-  getAttachmentList = () => {
-    const db = encodeURIComponent(this.props.doc.database.get('id'));
-    const doc = encodeURIComponent(this.props.doc.get('_id'));
-
-    return _.map(this.props.doc.get('_attachments'), (item, filename) => {
-      const url = FauxtonAPI.urls('document', 'attachment', db, doc, encodeURIComponent(filename));
-      return (
-        <MenuItem key={filename} href={url} target="_blank" data-bypass="true">
-          <strong>{filename}</strong>
-          <span className="attachment-delimiter">-</span>
-          <span>{item.content_type}{item.content_type ? ', ' : ''}{Helpers.formatSize(item.length)}</span>
-        </MenuItem>
-      );
-    });
-  };
-
-  render() {
-    if (this.props.isLoading || !this.props.doc.get('_attachments')) {
-      return false;
-    }
-
-    return (
-      <div className="panel-section view-attachments-section btn-group">
-        <Dropdown id="view-attachments-menu">
-          <Dropdown.Toggle noCaret className="panel-button dropdown-toggle btn" data-bypass="true">
-            <i className="icon icon-paper-clip"></i>
-            <span className="button-text">View Attachments</span>
-            <span className="caret"></span>
-          </Dropdown.Toggle>
-          <Dropdown.Menu>
-            {this.getAttachmentList()}
-          </Dropdown.Menu>
-        </Dropdown>
-      </div>
-    );
-  }
-}
-
-class PanelButton extends React.Component {
-  static propTypes = {
-    title: PropTypes.string.isRequired,
-    onClick: PropTypes.func.isRequired,
-    className: PropTypes.string
-  };
-
-  static defaultProps = {
-    title: '',
-    iconClass: '',
-    onClick: function () { },
-    className: ''
-  };
-
-  render() {
-    var iconClasses = 'icon ' + this.props.iconClass;
-    return (
-      <div className="panel-section">
-        <button className={`panel-button ${this.props.className}`} title={this.props.title} onClick={this.props.onClick}>
-          <i className={iconClasses}></i>
-          <span>{this.props.title}</span>
-        </button>
-      </div>
-    );
-  }
-}
-
-class UploadModal extends React.Component {
-  static propTypes = {
-    visible: PropTypes.bool.isRequired,
-    doc: PropTypes.object
-  };
-
-  getStoreState = () => {
-    return {
-      inProgress: store.isUploadInProgress(),
-      loadPercentage: store.getUploadLoadPercentage(),
-      errorMessage: store.getFileUploadErrorMsg()
-    };
-  };
-
-  componentDidMount() {
-    store.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    store.off('change', this.onChange);
-  }
-
-  onChange = () => {
-    this.setState(this.getStoreState());
-  };
-
-  closeModal = (e) => {
-    if (e) {
-      e.preventDefault();
-    }
-
-    if (this.state.inProgress) {
-      Actions.cancelUpload();
-    }
-    Actions.hideUploadModal();
-    Actions.resetUploadModal();
-  };
-
-  upload = () => {
-    Actions.uploadAttachment({
-      doc: this.props.doc,
-      rev: this.props.doc.get('_rev'),
-      files: this.attachments.files
-    });
-  };
-
-  state = this.getStoreState();
-
-  render() {
-    let errorClasses = 'alert alert-error';
-    if (this.state.errorMessage === '') {
-      errorClasses += ' hide';
-    }
-    let loadIndicatorClasses = 'progress progress-info';
-    let disabledAttribute = {disabled: 'disabled'};
-    if (!this.state.inProgress) {
-      loadIndicatorClasses += ' hide';
-      disabledAttribute = {};
-    }
-
-    return (
-      <Modal dialogClassName="upload-file-modal" show={this.props.visible} onHide={this.closeModal}>
-        <Modal.Header closeButton={true}>
-          <Modal.Title>Upload Attachment</Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
-          <div className={errorClasses}>{this.state.errorMessage}</div>
-          <div>
-            <form ref={node => this.uploadForm = node} className="form">
-              <p>
-                Select a file to upload as an attachment to this document. Uploading a file saves the document as a new
-                revision.
-              </p>
-              <input ref={el => this.attachments = el} type="file" name="_attachments" {...disabledAttribute}/>
-              <br />
-            </form>
-
-            <div className={loadIndicatorClasses}>
-              <div className="bar" style={{ width: this.state.loadPercentage + '%'}}></div>
-            </div>
-          </div>
-        </Modal.Body>
-        <Modal.Footer>
-          <a href="#" data-bypass="true" className="cancel-link" onClick={this.closeModal}>Cancel</a>
-          <button href="#" id="upload-btn" data-bypass="true" className="btn btn-primary save" onClick={this.upload} {...disabledAttribute}>
-            <i className="icon icon-upload" /> Upload Attachment
-          </button>
-        </Modal.Footer>
-      </Modal>
-    );
-  }
-}
-
-class CloneDocModal extends React.Component {
-  static propTypes = {
-    visible: PropTypes.bool.isRequired,
-    doc: PropTypes.object,
-    database: PropTypes.object.isRequired,
-    onSubmit: PropTypes.func.isRequired
-  };
-
-  state = {
-    uuid: null
-  };
-
-  cloneDoc = () => {
-    if (this.props.onSubmit) {
-      this.props.onSubmit();
-    }
-
-    Actions.cloneDoc(this.props.database, this.props.doc, this.state.uuid);
-  };
-
-  componentDidUpdate() {
-    if (this.state.uuid === null) {
-      Helpers.getUUID().then((res) => {
-        if (res.uuids) {
-          this.setState({ uuid: res.uuids[0] });
-        }
-      }).catch(() => {});
-    }
-  }
-
-  closeModal = (e) => {
-    if (e) {
-      e.preventDefault();
-    }
-    Actions.hideCloneDocModal();
-  };
-
-  docIDChange = (e) => {
-    this.setState({ uuid: e.target.value });
-  };
-
-  render() {
-    if (this.state.uuid === null) {
-      return false;
-    }
-
-    return (
-      <Modal dialogClassName="clone-doc-modal" show={this.props.visible} onHide={this.closeModal}>
-        <Modal.Header closeButton={true}>
-          <Modal.Title>Clone Document</Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
-          <form className="form" onSubmit={(e) => { e.preventDefault(); this.cloneDoc(); }}>
-            <p>
-              Document cloning copies the saved version of the document. Unsaved document changes will be discarded.
-            </p>
-            <p>
-              You can modify the following generated ID for your new document.
-            </p>
-            <input ref={node => this.newDocId = node} type="text" autoFocus={true} className="input-block-level"
-              onChange={this.docIDChange} value={this.state.uuid} />
-          </form>
-        </Modal.Body>
-        <Modal.Footer>
-          <a href="#" data-bypass="true" className="cancel-link" onClick={this.closeModal}>Cancel</a>
-          <button className="btn btn-primary save" onClick={this.cloneDoc}>
-            <i className="icon-repeat"></i> Clone Document
-          </button>
-        </Modal.Footer>
-      </Modal>
-    );
-  }
-}
-
-
-export default {
-  DocEditorController: DocEditorController,
-  AttachmentsPanelButton: AttachmentsPanelButton,
-  UploadModal: UploadModal,
-  CloneDocModal: CloneDocModal
-};
diff --git a/app/addons/documents/doc-editor/components/AttachmentsPanelButton.js b/app/addons/documents/doc-editor/components/AttachmentsPanelButton.js
new file mode 100644
index 000000000..043e33313
--- /dev/null
+++ b/app/addons/documents/doc-editor/components/AttachmentsPanelButton.js
@@ -0,0 +1,68 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import FauxtonAPI from '../../../../core/api';
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Dropdown, MenuItem } from 'react-bootstrap';
+import Helpers from '../../../../helpers';
+
+
+export default class AttachmentsPanelButton extends React.Component {
+  static propTypes = {
+    isLoading: PropTypes.bool.isRequired,
+    doc: PropTypes.object
+  };
+
+  static defaultProps = {
+    isLoading: true,
+    doc: {}
+  };
+
+  getAttachmentList = () => {
+    const db = encodeURIComponent(this.props.doc.database.get('id'));
+    const doc = encodeURIComponent(this.props.doc.get('_id'));
+
+    return _.map(this.props.doc.get('_attachments'), (item, filename) => {
+      const url = FauxtonAPI.urls('document', 'attachment', db, doc, encodeURIComponent(filename));
+      return (
+        <MenuItem key={filename} href={url} target="_blank" data-bypass="true">
+          <strong>{filename}</strong>
+          <span className="attachment-delimiter">-</span>
+          <span>{item.content_type}{item.content_type ? ', ' : ''}{Helpers.formatSize(item.length)}</span>
+        </MenuItem>
+      );
+    });
+  };
+
+  render() {
+    if (this.props.isLoading || !this.props.doc.get('_attachments')) {
+      return false;
+    }
+
+    return (
+      <div className="panel-section view-attachments-section btn-group">
+        <Dropdown id="view-attachments-menu">
+          <Dropdown.Toggle noCaret className="panel-button dropdown-toggle btn" data-bypass="true">
+            <i className="icon icon-paper-clip"></i>
+            <span className="button-text">View Attachments</span>
+            <span className="caret"></span>
+          </Dropdown.Toggle>
+          <Dropdown.Menu>
+            {this.getAttachmentList()}
+          </Dropdown.Menu>
+        </Dropdown>
+      </div>
+    );
+  }
+}
diff --git a/app/addons/documents/doc-editor/components/CloneDocModal.js b/app/addons/documents/doc-editor/components/CloneDocModal.js
new file mode 100644
index 000000000..3d6776e68
--- /dev/null
+++ b/app/addons/documents/doc-editor/components/CloneDocModal.js
@@ -0,0 +1,94 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Modal } from 'react-bootstrap';
+import Helpers from '../../../../helpers';
+
+
+export default class CloneDocModal extends React.Component {
+  static propTypes = {
+    visible: PropTypes.bool.isRequired,
+    doc: PropTypes.object,
+    database: PropTypes.object.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+    hideCloneDocModal: PropTypes.func.isRequired,
+    cloneDoc: PropTypes.func.isRequired
+  };
+
+  state = {
+    uuid: null
+  };
+
+  cloneDoc = () => {
+    if (this.props.onSubmit) {
+      this.props.onSubmit();
+    }
+
+    this.props.cloneDoc(this.props.database, this.props.doc, this.state.uuid);
+  };
+
+  componentDidUpdate() {
+    if (this.state.uuid === null) {
+      Helpers.getUUID().then((res) => {
+        if (res.uuids) {
+          this.setState({ uuid: res.uuids[0] });
+        }
+      }).catch(() => {});
+    }
+  }
+
+  closeModal = (e) => {
+    if (e) {
+      e.preventDefault();
+    }
+    this.props.hideCloneDocModal();
+  };
+
+  docIDChange = (e) => {
+    this.setState({ uuid: e.target.value });
+  };
+
+  render() {
+    if (this.state.uuid === null) {
+      return false;
+    }
+
+    return (
+      <Modal dialogClassName="clone-doc-modal" show={this.props.visible} onHide={this.closeModal}>
+        <Modal.Header closeButton={true}>
+          <Modal.Title>Clone Document</Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+          <form className="form" onSubmit={(e) => { e.preventDefault(); this.cloneDoc(); }}>
+            <p>
+              Document cloning copies the saved version of the document. Unsaved document changes will be discarded.
+            </p>
+            <p>
+              You can modify the following generated ID for your new document.
+            </p>
+            <input ref={node => this.newDocId = node} type="text" autoFocus={true} className="input-block-level"
+              onChange={this.docIDChange} value={this.state.uuid} />
+          </form>
+        </Modal.Body>
+        <Modal.Footer>
+          <a href="#" data-bypass="true" className="cancel-link" onClick={this.closeModal}>Cancel</a>
+          <button className="btn btn-primary save" onClick={this.cloneDoc}>
+            <i className="icon-repeat"></i> Clone Document
+          </button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+}
diff --git a/app/addons/documents/doc-editor/components/DocEditorContainer.js b/app/addons/documents/doc-editor/components/DocEditorContainer.js
new file mode 100644
index 000000000..3946ca3f9
--- /dev/null
+++ b/app/addons/documents/doc-editor/components/DocEditorContainer.js
@@ -0,0 +1,74 @@
+import { connect } from 'react-redux';
+import Actions from '../actions';
+import DocEditorScreen from './DocEditorScreen';
+
+const mapStateToProps = ({ docEditor }, ownProps) => {
+  return {
+    isLoading: docEditor.isLoading,
+    isNewDoc: ownProps.isNewDoc,
+    doc: docEditor.doc,
+    database: ownProps.database,
+    conflictCount: docEditor.docConflictCount,
+
+    isCloneDocModalVisible: docEditor.cloneDocModalVisible,
+
+    isDeleteDocModalVisible: docEditor.deleteDocModalVisible,
+
+    isUploadModalVisible: docEditor.uploadModalVisible,
+    uploadInProgress: docEditor.uploadInProgress,
+    uploadPercentage: docEditor.uploadPercentage,
+    uploadErrorMessage: docEditor.uploadErrorMessage,
+    numFilesUploaded: docEditor.numFilesUploaded
+  };
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    saveDoc: (doc, isValidDoc, onSave) => {
+      Actions.saveDoc(doc, isValidDoc, onSave);
+    },
+
+    showCloneDocModal: () => {
+      dispatch(Actions.showCloneDocModal());
+    },
+    hideCloneDocModal: () => {
+      dispatch(Actions.hideCloneDocModal());
+    },
+    cloneDoc: (database, doc, newId) => {
+      Actions.cloneDoc(database, doc, newId);
+    },
+
+    showDeleteDocModal: () => {
+      dispatch(Actions.showDeleteDocModal());
+    },
+    hideDeleteDocModal: () => {
+      dispatch(Actions.hideDeleteDocModal());
+    },
+    deleteDoc: (doc) => {
+      Actions.deleteDoc(doc);
+    },
+
+    showUploadModal: () => {
+      dispatch(Actions.showUploadModal());
+    },
+    hideUploadModal: () => {
+      dispatch(Actions.hideUploadModal());
+    },
+    cancelUpload: () => {
+      Actions.cancelUpload();
+    },
+    resetUploadModal: () => {
+      dispatch(Actions.resetUploadModal());
+    },
+    uploadAttachment: (params) => {
+      dispatch(Actions.uploadAttachment(params));
+    }
+  };
+};
+
+const DocEditorContainer = connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(DocEditorScreen);
+
+export default DocEditorContainer;
diff --git a/app/addons/documents/doc-editor/components/DocEditorScreen.js b/app/addons/documents/doc-editor/components/DocEditorScreen.js
new file mode 100644
index 000000000..6518d04a2
--- /dev/null
+++ b/app/addons/documents/doc-editor/components/DocEditorScreen.js
@@ -0,0 +1,214 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import FauxtonAPI from '../../../../core/api';
+import FauxtonComponents from '../../../fauxton/components';
+import GeneralComponents from '../../../components/react-components';
+import AttachmentsPanelButton from './AttachmentsPanelButton';
+import CloneDocModal from './CloneDocModal';
+import PanelButton from './PanelButton';
+import UploadModal from './UploadModal';
+
+
+export default class DocEditorScreen extends React.Component {
+  static defaultProps = {
+    database: {},
+    isNewDoc: false
+  };
+
+  static propTypes = {
+    isLoading: PropTypes.bool.isRequired,
+    isNewDoc: PropTypes.bool.isRequired,
+    doc: PropTypes.object,
+    conflictCount: PropTypes.number.isRequired,
+    saveDoc: PropTypes.func.isRequired,
+
+    isCloneDocModalVisible: PropTypes.bool.isRequired,
+    database: PropTypes.object,
+    showCloneDocModal: PropTypes.func.isRequired,
+    hideCloneDocModal: PropTypes.func.isRequired,
+    cloneDoc: PropTypes.func.isRequired,
+
+    isDeleteDocModalVisible: PropTypes.bool.isRequired,
+    showDeleteDocModal: PropTypes.func.isRequired,
+    hideDeleteDocModal: PropTypes.func.isRequired,
+    deleteDoc: PropTypes.func.isRequired,
+
+    isUploadModalVisible: PropTypes.bool.isRequired,
+    uploadInProgress: PropTypes.bool.isRequired,
+    uploadPercentage: PropTypes.number.isRequired,
+    uploadErrorMessage: PropTypes.string,
+    numFilesUploaded: PropTypes.number.isRequired,
+    showUploadModal: PropTypes.func.isRequired,
+    hideUploadModal: PropTypes.func.isRequired,
+    cancelUpload: PropTypes.func.isRequired,
+    resetUploadModal: PropTypes.func.isRequired,
+    uploadAttachment: PropTypes.func.isRequired
+  }
+
+  getCodeEditor = () => {
+    if (this.props.isLoading) {
+      return (<GeneralComponents.LoadLines />);
+    }
+
+    var code = JSON.stringify(this.props.doc.attributes, null, '  ');
+    var editorCommands = [{
+      name: 'save',
+      bindKey: { win: 'Ctrl-S', mac: 'Ctrl-S' },
+      exec: this.saveDoc
+    }];
+
+    return (
+      <GeneralComponents.CodeEditor
+        id="doc-editor"
+        ref={node => this.docEditor = node}
+        defaultCode={code}
+        mode="json"
+        autoFocus={true}
+        editorCommands={editorCommands}
+        notifyUnsavedChanges={true}
+        stringEditModalEnabled={true} />
+    );
+  };
+
+  UNSAFE_componentWillUpdate(nextProps) {
+    // Update the editor whenever a file is uploaded, a doc is cloned, or a new doc is loaded
+    if (this.props.numFilesUploaded !== nextProps.numFilesUploaded ||
+        this.props.doc && this.props.doc.hasChanged() ||
+        (this.props.doc && nextProps.doc && this.props.doc.id !== nextProps.doc.id)) {
+      this.getEditor().setValue(JSON.stringify(nextProps.doc.attributes, null, '  '));
+      this.onSaveComplete();
+    }
+  }
+
+  saveDoc = () => {
+    this.props.saveDoc(this.props.doc, this.checkDocIsValid(), this.onSaveComplete);
+  };
+
+  onSaveComplete = () => {
+    this.getEditor().clearChanges();
+  };
+
+  hideDeleteDocModal = () => {
+    this.props.hideDeleteDocModal();
+  };
+
+  deleteDoc = () => {
+    this.props.hideDeleteDocModal();
+    this.props.deleteDoc(this.props.doc);
+  };
+
+  getEditor = () => {
+    return (this.docEditor) ? this.docEditor.getEditor() : null;
+  };
+
+  checkDocIsValid = () => {
+    if (this.getEditor().hasErrors()) {
+      return false;
+    }
+    var json = JSON.parse(this.getEditor().getValue());
+    this.props.doc.clear().set(json, { validate: true });
+
+    return !this.props.doc.validationError;
+  };
+
+  clearChanges = () => {
+    this.docEditor.clearChanges();
+  };
+
+  getExtensionIcons = () => {
+    var extensions = FauxtonAPI.getExtensions('DocEditor:icons');
+    return _.map(extensions, (Extension, i) => {
+      return (<Extension doc={this.props.doc} key={i} database={this.props.database} />);
+    });
+  };
+
+  getButtonRow = () => {
+    if (this.props.isNewDoc) {
+      return false;
+    }
+    return (
+      <div>
+        <AttachmentsPanelButton doc={this.props.doc} isLoading={this.props.isLoading} />
+        <div className="doc-editor-extension-icons">{this.getExtensionIcons()}</div>
+
+        {this.props.conflictCount ? <PanelButton
+          title={`Conflicts (${this.props.conflictCount})`}
+          iconClass="icon-columns"
+          className="conflicts"
+          onClick={() => { FauxtonAPI.navigate(FauxtonAPI.urls('revision-browser', 'app', this.props.database.safeID(), this.props.doc.id));}}/> : null}
+
+        <PanelButton className="upload" title="Upload Attachment" iconClass="icon-circle-arrow-up" onClick={this.props.showUploadModal} />
+        <PanelButton title="Clone Document" iconClass="icon-repeat" onClick={this.props.showCloneDocModal} />
+        <PanelButton title="Delete" iconClass="icon-trash" onClick={this.props.showDeleteDocModal} />
+      </div>
+    );
+  };
+
+  render() {
+    var saveButtonLabel = (this.props.isNewDoc) ? 'Create Document' : 'Save Changes';
+    let endpoint = FauxtonAPI.urls('allDocs', 'app', FauxtonAPI.url.encode(this.props.database.id));
+    return (
+      <div>
+        <div id="doc-editor-actions-panel">
+          <div className="doc-actions-left">
+            <button className="save-doc btn btn-primary save" type="button" onClick={this.saveDoc}>
+              <i className="icon fonticon-ok-circled"></i> {saveButtonLabel}
+            </button>
+            <div>
+              <a href={`#/${endpoint}`} className="js-back cancel-button">Cancel</a>
+            </div>
+          </div>
+          <div className="alignRight">
+            {this.getButtonRow()}
+          </div>
+        </div>
+
+        <div className="code-region">
+          <div className="bgEditorGutter"></div>
+          <div id="editor-container" className="doc-code">{this.getCodeEditor()}</div>
+
+        </div>
+
+        <UploadModal
+          ref={node => this.uploadModal = node}
+          visible={this.props.isUploadModalVisible}
+          doc={this.props.doc}
+          inProgress={this.props.uploadInProgress}
+          uploadPercentage={this.props.uploadPercentage}
+          errorMessage={this.props.uploadErrorMessage}
+          cancelUpload={this.props.cancelUpload}
+          hideUploadModal={this.props.hideUploadModal}
+          resetUploadModal={this.props.resetUploadModal}
+          uploadAttachment={this.props.uploadAttachment}/>
+        <CloneDocModal
+          doc={this.props.doc}
+          database={this.props.database}
+          visible={this.props.isCloneDocModalVisible}
+          onSubmit={this.clearChanges}
+          hideCloneDocModal={this.props.hideCloneDocModal}
+          cloneDoc={this.props.cloneDoc}/>
+        <span id='hey'>bb</span>
+        <FauxtonComponents.ConfirmationModal
+          title="Confirm Deletion"
+          visible={this.props.isDeleteDocModalVisible}
+          text="Are you sure you want to delete this document?"
+          onClose={this.hideDeleteDocModal}
+          onSubmit={this.deleteDoc}
+          successButtonLabel="Delete Document" />
+      </div>
+    );
+  }
+}
diff --git a/app/addons/documents/doc-editor/components/PanelButton.js b/app/addons/documents/doc-editor/components/PanelButton.js
new file mode 100644
index 000000000..fd7dc6497
--- /dev/null
+++ b/app/addons/documents/doc-editor/components/PanelButton.js
@@ -0,0 +1,43 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+
+export default class PanelButton extends React.Component {
+  static propTypes = {
+    title: PropTypes.string.isRequired,
+    onClick: PropTypes.func.isRequired,
+    className: PropTypes.string
+  };
+
+  static defaultProps = {
+    title: '',
+    iconClass: '',
+    onClick: () => { },
+    className: ''
+  };
+
+  render() {
+    var iconClasses = 'icon ' + this.props.iconClass;
+    return (
+      <div className="panel-section">
+        <button className={`panel-button ${this.props.className}`} title={this.props.title} onClick={this.props.onClick}>
+          <i className={iconClasses}></i>
+          <span>{this.props.title}</span>
+        </button>
+      </div>
+    );
+  }
+}
diff --git a/app/addons/documents/doc-editor/components/UploadModal.js b/app/addons/documents/doc-editor/components/UploadModal.js
new file mode 100644
index 000000000..eb907f5e7
--- /dev/null
+++ b/app/addons/documents/doc-editor/components/UploadModal.js
@@ -0,0 +1,95 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Modal } from 'react-bootstrap';
+
+
+export default class UploadModal extends React.Component {
+  static propTypes = {
+    visible: PropTypes.bool.isRequired,
+    doc: PropTypes.object,
+    inProgress: PropTypes.bool.isRequired,
+    uploadPercentage: PropTypes.number.isRequired,
+    errorMessage: PropTypes.string,
+    cancelUpload: PropTypes.func.isRequired,
+    hideUploadModal: PropTypes.func.isRequired,
+    resetUploadModal: PropTypes.func.isRequired,
+    uploadAttachment: PropTypes.func.isRequired
+  };
+
+  closeModal = (e) => {
+    if (e) {
+      e.preventDefault();
+    }
+
+    if (this.props.inProgress) {
+      this.props.cancelUpload();
+    }
+    this.props.hideUploadModal();
+    this.props.resetUploadModal();
+  };
+
+  upload = () => {
+    this.props.uploadAttachment({
+      doc: this.props.doc,
+      rev: this.props.doc.get('_rev'),
+      files: this.attachments.files
+    });
+  };
+
+  render() {
+    let errorClasses = 'alert alert-error';
+    if (this.props.errorMessage === '') {
+      errorClasses += ' hide';
+    }
+    let loadIndicatorClasses = 'progress progress-info';
+    let disabledAttribute = {disabled: 'disabled'};
+    if (!this.props.inProgress) {
+      loadIndicatorClasses += ' hide';
+      disabledAttribute = {};
+    }
+
+    return (
+      <Modal dialogClassName="upload-file-modal" show={this.props.visible} onHide={this.closeModal}>
+        <Modal.Header closeButton={true}>
+          <Modal.Title>Upload Attachment</Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+          <div className={errorClasses}>{this.props.errorMessage}</div>
+          <div>
+            <form ref={node => this.uploadForm = node} className="form">
+              <p>
+                Select a file to upload as an attachment to this document. Uploading a file saves the document as a new
+                revision.
+              </p>
+              <input ref={el => this.attachments = el} type="file" name="_attachments" {...disabledAttribute}/>
+              <br />
+            </form>
+
+            <div className={loadIndicatorClasses}>
+              <div className="bar" style={{ width: this.props.uploadPercentage + '%'}}></div>
+            </div>
+          </div>
+        </Modal.Body>
+        <Modal.Footer>
+          <a href="#" data-bypass="true" className="cancel-link" onClick={this.closeModal}>Cancel</a>
+          <button href="#" id="upload-btn" data-bypass="true" className="btn btn-primary save" onClick={this.upload} {...disabledAttribute}>
+            <i className="icon icon-upload" /> Upload Attachment
+          </button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+}
diff --git a/app/addons/documents/doc-editor/reducers.js b/app/addons/documents/doc-editor/reducers.js
new file mode 100644
index 000000000..4945e30bb
--- /dev/null
+++ b/app/addons/documents/doc-editor/reducers.js
@@ -0,0 +1,124 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import ActionTypes from "./actiontypes";
+
+const initialState = {
+  doc: null,
+  isLoading: true,
+  cloneDocModalVisible: false,
+  deleteDocModalVisible: false,
+  uploadModalVisible: false,
+
+  numFilesUploaded: 0,
+  uploadErrorMessage: '',
+  uploadInProgress: false,
+  uploadPercentage: 0,
+
+  docConflictCount: 0
+};
+
+export default function docEditor (state = initialState, action) {
+  const { options, type } = action;
+  switch (type) {
+
+    case ActionTypes.RESET_DOC:
+      return {
+        ...initialState
+      };
+
+    case ActionTypes.DOC_LOADED:
+      const conflictCount = options.doc.get('_conflicts') ? options.doc.get('_conflicts').length : 0;
+      options.doc.unset('_conflicts');
+      return {
+        ...state,
+        isLoading: false,
+        doc: options.doc,
+        docConflictCount: conflictCount,
+      };
+
+    case ActionTypes.SHOW_CLONE_DOC_MODAL:
+      return {
+        ...state,
+        cloneDocModalVisible: true
+      };
+
+    case ActionTypes.HIDE_CLONE_DOC_MODAL:
+      return {
+        ...state,
+        cloneDocModalVisible: false
+      };
+
+    case ActionTypes.SHOW_DELETE_DOC_CONFIRMATION_MODAL:
+      return {
+        ...state,
+        deleteDocModalVisible: true
+      };
+
+    case ActionTypes.HIDE_DELETE_DOC_CONFIRMATION_MODAL:
+      return {
+        ...state,
+        deleteDocModalVisible: false
+      };
+
+    case ActionTypes.SHOW_UPLOAD_MODAL:
+      return {
+        ...state,
+        uploadModalVisible: true
+      };
+
+    case ActionTypes.HIDE_UPLOAD_MODAL:
+      return {
+        ...state,
+        uploadModalVisible: false
+      };
+
+    case ActionTypes.FILE_UPLOAD_SUCCESS:
+      return {
+        ...state,
+        numFilesUploaded: state.numFilesUploaded + 1
+      };
+
+    case ActionTypes.FILE_UPLOAD_ERROR:
+      return {
+        ...state,
+        uploadInProgress: false,
+        uploadPercentage: 0,
+        uploadErrorMessage: options.error
+      };
+
+    case ActionTypes.RESET_UPLOAD_MODAL:
+      return {
+        ...state,
+        uploadInProgress: false,
+        uploadPercentage: 0,
+        uploadErrorMessage: ''
+      };
+
+    case ActionTypes.START_FILE_UPLOAD:
+      return {
+        ...state,
+        uploadInProgress: true,
+        uploadPercentage: 0,
+        fileUploadErrorMsg: ''
+      };
+
+    case ActionTypes.SET_FILE_UPLOAD_PERCENTAGE:
+      return {
+        ...state,
+        uploadPercentage: options.percent
+      };
+
+    default:
+      return state;
+  }
+}
diff --git a/app/addons/documents/doc-editor/stores.js b/app/addons/documents/doc-editor/stores.js
deleted file mode 100644
index b3806da43..000000000
--- a/app/addons/documents/doc-editor/stores.js
+++ /dev/null
@@ -1,205 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../core/api";
-import ActionTypes from "./actiontypes";
-var Stores = {};
-
-Stores.DocEditorStore = FauxtonAPI.Store.extend({
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._doc = null;
-    this._isLoading = true;
-    this._cloneDocModalVisible = false;
-    this._deleteDocModalVisible = false;
-    this._uploadModalVisible = false;
-
-    // file upload-related fields
-    this._numFilesUploaded = 0;
-    this._fileUploadErrorMsg = '';
-    this._uploadInProgress = false;
-    this._fileUploadLoadPercentage = 0;
-
-    this._docConflictCount = null;
-  },
-
-  isLoading: function () {
-    return this._isLoading;
-  },
-
-  getDocConflictCount: function () {
-    return this._docConflictCount;
-  },
-
-  docLoaded: function (options) {
-    this._isLoading = false;
-    this._docConflictCount = options.doc.get('_conflicts') ? options.doc.get('_conflicts').length : 0;
-    options.doc.unset('_conflicts');
-    this._doc = options.doc;
-  },
-
-  getDoc: function () {
-    return this._doc;
-  },
-
-  isCloneDocModalVisible: function () {
-    return this._cloneDocModalVisible;
-  },
-
-  showCloneDocModal: function () {
-    this._cloneDocModalVisible = true;
-  },
-
-  hideCloneDocModal: function () {
-    this._cloneDocModalVisible = false;
-  },
-
-  isDeleteDocModalVisible: function () {
-    return this._deleteDocModalVisible;
-  },
-
-  showDeleteDocModal: function () {
-    this._deleteDocModalVisible = true;
-  },
-
-  hideDeleteDocModal: function () {
-    this._deleteDocModalVisible = false;
-  },
-
-  isUploadModalVisible: function () {
-    return this._uploadModalVisible;
-  },
-
-  showUploadModal: function () {
-    this._uploadModalVisible = true;
-  },
-
-  hideUploadModal: function () {
-    this._uploadModalVisible = false;
-  },
-
-  getNumFilesUploaded: function () {
-    return this._numFilesUploaded;
-  },
-
-  getFileUploadErrorMsg: function () {
-    return this._fileUploadErrorMsg;
-  },
-
-  setFileUploadErrorMsg: function (error) {
-    this._uploadInProgress = false;
-    this._fileUploadLoadPercentage = 0;
-    this._fileUploadErrorMsg = error;
-  },
-
-  isUploadInProgress: function () {
-    return this._uploadInProgress;
-  },
-
-  getUploadLoadPercentage: function () {
-    return this._fileUploadLoadPercentage;
-  },
-
-  resetUploadModal: function () {
-    this._uploadInProgress = false;
-    this._fileUploadLoadPercentage = 0;
-    this._fileUploadErrorMsg = '';
-  },
-
-  startFileUpload: function () {
-    this._uploadInProgress = true;
-    this._fileUploadLoadPercentage = 0;
-    this._fileUploadErrorMsg = '';
-  },
-
-  dispatch: function (action) {
-    switch (action.type) {
-      case ActionTypes.RESET_DOC:
-        this.reset();
-        break;
-
-      case ActionTypes.DOC_LOADED:
-        this.docLoaded(action.options);
-        this.triggerChange();
-        break;
-
-      case ActionTypes.SHOW_CLONE_DOC_MODAL:
-        this.showCloneDocModal();
-        this.triggerChange();
-        break;
-
-      case ActionTypes.HIDE_CLONE_DOC_MODAL:
-        this.hideCloneDocModal();
-        this.triggerChange();
-        break;
-
-      case ActionTypes.SHOW_DELETE_DOC_CONFIRMATION_MODAL:
-        this.showDeleteDocModal();
-        this.triggerChange();
-        break;
-
-      case ActionTypes.HIDE_DELETE_DOC_CONFIRMATION_MODAL:
-        this.hideDeleteDocModal();
-        this.triggerChange();
-        break;
-
-      case ActionTypes.SHOW_UPLOAD_MODAL:
-        this.showUploadModal();
-        this.triggerChange();
-        break;
-
-      case ActionTypes.HIDE_UPLOAD_MODAL:
-        this.hideUploadModal();
-        this.triggerChange();
-        break;
-
-      case ActionTypes.FILE_UPLOAD_SUCCESS:
-        this._numFilesUploaded++;
-        this.triggerChange();
-        break;
-
-      case ActionTypes.FILE_UPLOAD_ERROR:
-        this.setFileUploadErrorMsg(action.options.error);
-        this.triggerChange();
-        break;
-
-      case ActionTypes.RESET_UPLOAD_MODAL:
-        this.resetUploadModal();
-        this.triggerChange();
-        break;
-
-      case ActionTypes.START_FILE_UPLOAD:
-        this.startFileUpload();
-        this.triggerChange();
-        break;
-
-      case ActionTypes.SET_FILE_UPLOAD_PERCENTAGE:
-        this._fileUploadLoadPercentage = action.options.percent;
-        this.triggerChange();
-        break;
-
-
-      default:
-        return;
-      // do nothing
-    }
-  }
-
-});
-
-Stores.docEditorStore = new Stores.DocEditorStore();
-Stores.docEditorStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.docEditorStore.dispatch.bind(Stores.docEditorStore));
-
-export default Stores;
diff --git a/app/addons/documents/routes-doc-editor.js b/app/addons/documents/routes-doc-editor.js
index aa0971093..f2509abc2 100644
--- a/app/addons/documents/routes-doc-editor.js
+++ b/app/addons/documents/routes-doc-editor.js
@@ -15,7 +15,7 @@ import FauxtonAPI from "../../core/api";
 import Documents from "./resources";
 import Databases from "../databases/base";
 import Actions from "./doc-editor/actions";
-import ReactComponents from "./doc-editor/components";
+import DocEditorContainer from "./doc-editor/components/DocEditorContainer";
 import RevBrowserContainer from './rev-browser/container';
 import {DocEditorLayout} from '../components/layouts';
 
@@ -77,13 +77,13 @@ const DocEditorRouteObject = FauxtonAPI.RouteObject.extend({
       this.doc = new Documents.Doc({ _id: docId }, { database: this.database, fetchConflicts: true });
     }
 
-    Actions.initDocEditor({ doc: this.doc, database: this.database });
+    Actions.dispatchInitDocEditor({ doc: this.doc, database: this.database });
 
     return <DocEditorLayout
       crumbs={crumbs}
       endpoint={this.doc.url('apiurl')}
       docURL={this.doc.documentation()}
-      component={<ReactComponents.DocEditorController
+      component={<DocEditorContainer
         database={this.database}
         isNewDoc={docId ? false : true}
       />}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services