couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gar...@apache.org
Subject [18/27] fauxton commit: updated refs/heads/master to 0ca35da
Date Tue, 31 May 2016 07:58:46 GMT
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/doc-editor/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/doc-editor/actions.js b/app/addons/documents/doc-editor/actions.js
index 0af33ba..ed00f94 100644
--- a/app/addons/documents/doc-editor/actions.js
+++ b/app/addons/documents/doc-editor/actions.js
@@ -12,239 +12,234 @@
 
 /* global FormData */
 
-define([
-  '../../../app',
-  '../../../core/api',
-  './actiontypes'
-],
-function (app, FauxtonAPI, ActionTypes) {
+import app from "../../../app";
+import FauxtonAPI from "../../../core/api";
+import ActionTypes from "./actiontypes";
 
-  var xhr;
+var xhr;
 
-  function initDocEditor (params) {
-    var doc = params.doc;
+function initDocEditor (params) {
+  var doc = params.doc;
 
-    // ensure a clean slate
-    FauxtonAPI.dispatch({ type: ActionTypes.RESET_DOC });
+  // ensure a clean slate
+  FauxtonAPI.dispatch({ type: ActionTypes.RESET_DOC });
 
-    doc.fetch().then(function () {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.DOC_LOADED,
-        options: {
-          doc: doc
-        }
-      });
-
-      if (params.onLoaded) {
-        params.onLoaded();
+  doc.fetch().then(function () {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DOC_LOADED,
+      options: {
+        doc: doc
       }
-    }, function (xhr, reason, msg) {
-      errorNotification('The document does not exist.');
-      FauxtonAPI.navigate(FauxtonAPI.urls('allDocs', 'app', params.database.id, ''));
     });
-  }
 
-  function saveDoc (doc, isValidDoc, onSave) {
-    if (isValidDoc) {
+    if (params.onLoaded) {
+      params.onLoaded();
+    }
+  }, function (xhr, reason, msg) {
+    errorNotification('The document does not exist.');
+    FauxtonAPI.navigate(FauxtonAPI.urls('allDocs', 'app', params.database.id, ''));
+  });
+}
+
+function saveDoc (doc, isValidDoc, onSave) {
+  if (isValidDoc) {
+    FauxtonAPI.addNotification({
+      msg: 'Saving document.',
+      clear: true
+    });
+
+    doc.save().then(function () {
+      onSave(doc.prettyJSON());
+      FauxtonAPI.navigate('#' + FauxtonAPI.urls('allDocs', 'app', doc.database.id), {trigger: true});
+    }).fail(function (xhr) {
       FauxtonAPI.addNotification({
-        msg: 'Saving document.',
+        msg: 'Save failed: ' + JSON.parse(xhr.responseText).reason,
+        type: 'error',
+        fade: false,
         clear: true
       });
+    });
 
-      doc.save().then(function () {
-        onSave(doc.prettyJSON());
-        FauxtonAPI.navigate('#' + FauxtonAPI.urls('allDocs', 'app', doc.database.id), {trigger: true});
-      }).fail(function (xhr) {
-        FauxtonAPI.addNotification({
-          msg: 'Save failed: ' + JSON.parse(xhr.responseText).reason,
-          type: 'error',
-          fade: false,
-          clear: true
-        });
+  } else if (doc.validationError && doc.validationError === 'Cannot change a documents id.') {
+    errorNotification('You cannot edit the _id of an existing document. Try this: Click \'Clone Document\', then change the _id on the clone before saving.');
+    delete doc.validationError;
+  } else {
+    errorNotification('Please fix the JSON errors and try saving again.');
+  }
+}
+
+function showDeleteDocModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.SHOW_DELETE_DOC_CONFIRMATION_MODAL });
+}
+
+function hideDeleteDocModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.HIDE_DELETE_DOC_CONFIRMATION_MODAL });
+}
+
+function deleteDoc (doc) {
+  var databaseName = doc.database.safeID();
+  var query = '?rev=' + doc.get('_rev');
+
+  $.ajax({
+    url: FauxtonAPI.urls('document', 'server', databaseName, doc.safeID(), query),
+    type: 'DELETE',
+    contentType: 'application/json; charset=UTF-8',
+    xhrFields: {
+      withCredentials: true
+    },
+    success: function () {
+      FauxtonAPI.addNotification({
+        msg: 'Your document has been successfully deleted.',
+        clear: true
+      });
+      FauxtonAPI.navigate(FauxtonAPI.urls('allDocs', 'app', databaseName, ''));
+    },
+    error: function (resp) {
+      FauxtonAPI.addNotification({
+        msg: 'Failed to delete your document!',
+        type: 'error',
+        clear: true
       });
-
-    } else if (doc.validationError && doc.validationError === 'Cannot change a documents id.') {
-      errorNotification('You cannot edit the _id of an existing document. Try this: Click \'Clone Document\', then change the _id on the clone before saving.');
-      delete doc.validationError;
-    } else {
-      errorNotification('Please fix the JSON errors and try saving again.');
     }
-  }
-
-  function showDeleteDocModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.SHOW_DELETE_DOC_CONFIRMATION_MODAL });
-  }
-
-  function hideDeleteDocModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.HIDE_DELETE_DOC_CONFIRMATION_MODAL });
-  }
-
-  function deleteDoc (doc) {
-    var databaseName = doc.database.safeID();
-    var query = '?rev=' + doc.get('_rev');
-
-    $.ajax({
-      url: FauxtonAPI.urls('document', 'server', databaseName, doc.safeID(), query),
-      type: 'DELETE',
-      contentType: 'application/json; charset=UTF-8',
-      xhrFields: {
-        withCredentials: true
-      },
-      success: function () {
-        FauxtonAPI.addNotification({
-          msg: 'Your document has been successfully deleted.',
-          clear: true
-        });
-        FauxtonAPI.navigate(FauxtonAPI.urls('allDocs', 'app', databaseName, ''));
-      },
-      error: function (resp) {
-        FauxtonAPI.addNotification({
-          msg: 'Failed to delete your document!',
-          type: 'error',
-          clear: true
-        });
+  });
+}
+
+function showCloneDocModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.SHOW_CLONE_DOC_MODAL });
+}
+
+function hideCloneDocModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.HIDE_CLONE_DOC_MODAL });
+}
+
+function cloneDoc (newId) {
+  var isDDoc = newId.match(/^_design\//),
+    removeDDocID = newId.replace(/^_design\//, ''),
+    encodedID = isDDoc ? '_design/' + app.utils.safeURLName(removeDDocID) : app.utils.safeURLName(newId);
+
+  this.hideCloneDocModal();
+  FauxtonAPI.triggerRouteEvent('duplicateDoc', encodedID);
+}
+
+function showUploadModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.SHOW_UPLOAD_MODAL });
+}
+
+function hideUploadModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.HIDE_UPLOAD_MODAL });
+}
+
+function uploadAttachment (params) {
+  if (params.files.length === 0) {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.FILE_UPLOAD_ERROR,
+      options: {
+        error: 'Please select a file to be uploaded.'
       }
     });
+    return;
   }
 
-  function showCloneDocModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.SHOW_CLONE_DOC_MODAL });
-  }
-
-  function hideCloneDocModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.HIDE_CLONE_DOC_MODAL });
-  }
-
-  function cloneDoc (newId) {
-    var isDDoc = newId.match(/^_design\//),
-      removeDDocID = newId.replace(/^_design\//, ''),
-      encodedID = isDDoc ? '_design/' + app.utils.safeURLName(removeDDocID) : app.utils.safeURLName(newId);
-
-    this.hideCloneDocModal();
-    FauxtonAPI.triggerRouteEvent('duplicateDoc', encodedID);
-  }
-
-  function showUploadModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.SHOW_UPLOAD_MODAL });
-  }
-
-  function hideUploadModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.HIDE_UPLOAD_MODAL });
-  }
+  FauxtonAPI.dispatch({ type: ActionTypes.START_FILE_UPLOAD });
+
+  // store the xhr in parent scope to allow us to cancel any uploads if the user closes the modal
+  xhr = $.ajaxSettings.xhr();
+
+  var query = '?rev=' + params.rev;
+  var db = params.doc.getDatabase().safeID();
+  var docId = params.doc.safeID();
+  var file = params.files[0];
+
+  $.ajax({
+    url: FauxtonAPI.urls('document', 'attachment', db, docId, file.name, query),
+    type: 'PUT',
+    data: file,
+    contentType: file.type,
+    processData: false,
+    xhrFields: {
+      withCredentials: true
+    },
+    xhr: function () {
+      xhr.upload.onprogress = function (evt) {
+        var percentComplete = evt.loaded / evt.total * 100;
+        FauxtonAPI.dispatch({
+          type: ActionTypes.SET_FILE_UPLOAD_PERCENTAGE,
+          options: {
+            percent: percentComplete
+          }
+        });
+      };
+      return xhr;
+    },
+    success: function () {
+
+      // re-initialize the document editor. Only announce it's been updated when
+      initDocEditor({
+        doc: params.doc,
+        onLoaded: function () {
+          FauxtonAPI.dispatch({ type: ActionTypes.FILE_UPLOAD_SUCCESS });
+          FauxtonAPI.addNotification({
+            msg: 'Document saved successfully.',
+            type: 'success',
+            clear: true
+          });
+        }.bind(this)
+      });
 
-  function uploadAttachment (params) {
-    if (params.files.length === 0) {
+    },
+    error: function (resp) {
+      // cancelled uploads throw an ajax error but they don't contain a response. We don't want to publish an error
+      // event in those cases
+      if (_.isEmpty(resp.responseText)) {
+        return;
+      }
       FauxtonAPI.dispatch({
         type: ActionTypes.FILE_UPLOAD_ERROR,
         options: {
-          error: 'Please select a file to be uploaded.'
+          error: JSON.parse(resp.responseText).reason
         }
       });
-      return;
     }
-
-    FauxtonAPI.dispatch({ type: ActionTypes.START_FILE_UPLOAD });
-
-    // store the xhr in parent scope to allow us to cancel any uploads if the user closes the modal
-    xhr = $.ajaxSettings.xhr();
-
-    var query = '?rev=' + params.rev;
-    var db = params.doc.getDatabase().safeID();
-    var docId = params.doc.safeID();
-    var file = params.files[0];
-
-    $.ajax({
-      url: FauxtonAPI.urls('document', 'attachment', db, docId, file.name, query),
-      type: 'PUT',
-      data: file,
-      contentType: file.type,
-      processData: false,
-      xhrFields: {
-        withCredentials: true
-      },
-      xhr: function () {
-        xhr.upload.onprogress = function (evt) {
-          var percentComplete = evt.loaded / evt.total * 100;
-          FauxtonAPI.dispatch({
-            type: ActionTypes.SET_FILE_UPLOAD_PERCENTAGE,
-            options: {
-              percent: percentComplete
-            }
-          });
-        };
-        return xhr;
-      },
-      success: function () {
-
-        // re-initialize the document editor. Only announce it's been updated when
-        initDocEditor({
-          doc: params.doc,
-          onLoaded: function () {
-            FauxtonAPI.dispatch({ type: ActionTypes.FILE_UPLOAD_SUCCESS });
-            FauxtonAPI.addNotification({
-              msg: 'Document saved successfully.',
-              type: 'success',
-              clear: true
-            });
-          }.bind(this)
-        });
-
-      },
-      error: function (resp) {
-        // cancelled uploads throw an ajax error but they don't contain a response. We don't want to publish an error
-        // event in those cases
-        if (_.isEmpty(resp.responseText)) {
-          return;
-        }
-        FauxtonAPI.dispatch({
-          type: ActionTypes.FILE_UPLOAD_ERROR,
-          options: {
-            error: JSON.parse(resp.responseText).reason
-          }
-        });
-      }
-    });
-  }
-
-  function cancelUpload () {
-    xhr.abort();
-  }
-
-  function resetUploadModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.RESET_UPLOAD_MODAL });
-  }
-
-
-  // helpers
-
-  function errorNotification (msg) {
-    FauxtonAPI.addNotification({
-      msg: msg,
-      type: 'error',
-      clear: true
-    });
-  }
-
-  return {
-    initDocEditor: initDocEditor,
-    saveDoc: saveDoc,
-
-    // clone doc
-    showCloneDocModal: showCloneDocModal,
-    hideCloneDocModal: hideCloneDocModal,
-    cloneDoc: cloneDoc,
-
-    // delete doc
-    showDeleteDocModal: showDeleteDocModal,
-    hideDeleteDocModal: hideDeleteDocModal,
-    deleteDoc: deleteDoc,
-
-    // upload modal
-    showUploadModal: showUploadModal,
-    hideUploadModal: hideUploadModal,
-    uploadAttachment: uploadAttachment,
-    cancelUpload: cancelUpload,
-    resetUploadModal: resetUploadModal
-  };
-
-});
+  });
+}
+
+function cancelUpload () {
+  xhr.abort();
+}
+
+function resetUploadModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.RESET_UPLOAD_MODAL });
+}
+
+
+// helpers
+
+function errorNotification (msg) {
+  FauxtonAPI.addNotification({
+    msg: msg,
+    type: 'error',
+    clear: true
+  });
+}
+
+export default {
+  initDocEditor: initDocEditor,
+  saveDoc: saveDoc,
+
+  // clone doc
+  showCloneDocModal: showCloneDocModal,
+  hideCloneDocModal: hideCloneDocModal,
+  cloneDoc: cloneDoc,
+
+  // delete doc
+  showDeleteDocModal: showDeleteDocModal,
+  hideDeleteDocModal: hideDeleteDocModal,
+  deleteDoc: deleteDoc,
+
+  // upload modal
+  showUploadModal: showUploadModal,
+  hideUploadModal: hideUploadModal,
+  uploadAttachment: uploadAttachment,
+  cancelUpload: cancelUpload,
+  resetUploadModal: resetUploadModal
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/doc-editor/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/doc-editor/actiontypes.js b/app/addons/documents/doc-editor/actiontypes.js
index e669155..25df4b9 100644
--- a/app/addons/documents/doc-editor/actiontypes.js
+++ b/app/addons/documents/doc-editor/actiontypes.js
@@ -10,22 +10,20 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([], function () {
-  return {
-    RESET_DOC: 'RESET_DOC',
-    DOC_LOADED: 'DOC_LOADED',
-    SHOW_CLONE_DOC_MODAL: 'SHOW_CLONE_DOC_MODAL',
-    HIDE_CLONE_DOC_MODAL: 'HIDE_CLONE_DOC_MODAL',
-    SHOW_DELETE_DOC_CONFIRMATION_MODAL: 'SHOW_DELETE_DOC_CONFIRMATION_MODAL',
-    HIDE_DELETE_DOC_CONFIRMATION_MODAL: 'HIDE_DELETE_DOC_CONFIRMATION_MODAL',
-    SHOW_UPLOAD_MODAL: 'SHOW_UPLOAD_MODAL',
-    HIDE_UPLOAD_MODAL: 'HIDE_UPLOAD_MODAL',
-    RESET_UPLOAD_MODAL: 'RESET_UPLOAD_MODAL',
-    SHOW_STRING_EDIT_MODAL: 'SHOW_STRING_EDIT_MODAL',
-    HIDE_STRING_EDIT_MODAL: 'HIDE_STRING_EDIT_MODAL',
-    FILE_UPLOAD_SUCCESS: 'FILE_UPLOAD_SUCCESS',
-    FILE_UPLOAD_ERROR: 'FILE_UPLOAD_ERROR',
-    START_FILE_UPLOAD: 'START_FILE_UPLOAD',
-    SET_FILE_UPLOAD_PERCENTAGE: 'SET_FILE_UPLOAD_PERCENTAGE'
-  };
-});
+export default {
+  RESET_DOC: 'RESET_DOC',
+  DOC_LOADED: 'DOC_LOADED',
+  SHOW_CLONE_DOC_MODAL: 'SHOW_CLONE_DOC_MODAL',
+  HIDE_CLONE_DOC_MODAL: 'HIDE_CLONE_DOC_MODAL',
+  SHOW_DELETE_DOC_CONFIRMATION_MODAL: 'SHOW_DELETE_DOC_CONFIRMATION_MODAL',
+  HIDE_DELETE_DOC_CONFIRMATION_MODAL: 'HIDE_DELETE_DOC_CONFIRMATION_MODAL',
+  SHOW_UPLOAD_MODAL: 'SHOW_UPLOAD_MODAL',
+  HIDE_UPLOAD_MODAL: 'HIDE_UPLOAD_MODAL',
+  RESET_UPLOAD_MODAL: 'RESET_UPLOAD_MODAL',
+  SHOW_STRING_EDIT_MODAL: 'SHOW_STRING_EDIT_MODAL',
+  HIDE_STRING_EDIT_MODAL: 'HIDE_STRING_EDIT_MODAL',
+  FILE_UPLOAD_SUCCESS: 'FILE_UPLOAD_SUCCESS',
+  FILE_UPLOAD_ERROR: 'FILE_UPLOAD_ERROR',
+  START_FILE_UPLOAD: 'START_FILE_UPLOAD',
+  SET_FILE_UPLOAD_PERCENTAGE: 'SET_FILE_UPLOAD_PERCENTAGE'
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/doc-editor/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/doc-editor/components.react.jsx b/app/addons/documents/doc-editor/components.react.jsx
index 8062dd7..c2a7f15 100644
--- a/app/addons/documents/doc-editor/components.react.jsx
+++ b/app/addons/documents/doc-editor/components.react.jsx
@@ -11,442 +11,436 @@
 // the License.
 
 
-define([
-  '../../../core/api',
-  '../../../app',
-  'react',
-  'react-dom',
-  './actions',
-  './stores',
-  '../../fauxton/components.react',
-  '../../components/react-components.react',
-  'react-bootstrap',
-  '../../../helpers',
-], function (FauxtonAPI, app, React, ReactDOM, Actions, Stores, FauxtonComponents, GeneralComponents, ReactBootstrap, Helpers) {
-
-  var store = Stores.docEditorStore;
-  var Modal = ReactBootstrap.Modal;
-
-
-  var DocEditorController = React.createClass({
-
-    getInitialState: function () {
-      return this.getStoreState();
-    },
-
-    getStoreState: function () {
-      return {
-        isLoading: store.isLoading(),
-        doc: store.getDoc(),
-        cloneDocModalVisible: store.isCloneDocModalVisible(),
-        uploadModalVisible: store.isUploadModalVisible(),
-        deleteDocModalVisible: store.isDeleteDocModalVisible(),
-        numFilesUploaded: store.getNumFilesUploaded(),
-        conflictCount: store.getDocConflictCount()
-      };
-    },
-
-    getDefaultProps: function () {
-      return {
-        database: {},
-        previousPage: '',
-        isNewDoc: false
-      };
-    },
-
-    getCodeEditor: function () {
-      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
-      }];
+import FauxtonAPI from "../../../core/api";
+import app from "../../../app";
+import React from "react";
+import ReactDOM from "react-dom";
+import Actions from "./actions";
+import Stores from "./stores";
+import FauxtonComponents from "../../fauxton/components.react";
+import GeneralComponents from "../../components/react-components.react";
+import { Modal } from "react-bootstrap";
+import Helpers from "../../../helpers";
+
+var store = Stores.docEditorStore;
+
+var DocEditorController = React.createClass({
+
+  getInitialState: function () {
+    return this.getStoreState();
+  },
+
+  getStoreState: function () {
+    return {
+      isLoading: store.isLoading(),
+      doc: store.getDoc(),
+      cloneDocModalVisible: store.isCloneDocModalVisible(),
+      uploadModalVisible: store.isUploadModalVisible(),
+      deleteDocModalVisible: store.isDeleteDocModalVisible(),
+      numFilesUploaded: store.getNumFilesUploaded(),
+      conflictCount: store.getDocConflictCount()
+    };
+  },
+
+  getDefaultProps: function () {
+    return {
+      database: {},
+      previousPage: '',
+      isNewDoc: false
+    };
+  },
+
+  getCodeEditor: function () {
+    if (this.state.isLoading) {
+      return (<GeneralComponents.LoadLines />);
+    }
 
-      return (
-        <GeneralComponents.CodeEditor
-          id="doc-editor"
-          ref="docEditor"
-          defaultCode={code}
-          mode="json"
-          autoFocus={true}
-          editorCommands={editorCommands}
-          notifyUnsavedChanges={true}
-          stringEditModalEnabled={true} />
-      );
-    },
-
-    componentDidMount: function () {
-      store.on('change', this.onChange, this);
-    },
-
-    componentWillUnmount: function () {
-      store.off('change', this.onChange);
-    },
-
-    // whenever a file is uploaded, reset the editor
-    componentWillUpdate: function (nextProps, nextState) {
-      if (this.state.numFilesUploaded !== nextState.numFilesUploaded) {
-        this.getEditor().setValue(JSON.stringify(nextState.doc.attributes, null, '  '));
-      }
-    },
-
-    onChange: function () {
-      if (this.isMounted()) {
-        this.setState(this.getStoreState());
-      }
-    },
-
-    saveDoc: function () {
-      Actions.saveDoc(this.state.doc, this.checkDocIsValid(), this.onSaveComplete);
-    },
-
-    onSaveComplete: function (json) {
-      this.getEditor().clearChanges();
-    },
-
-    hideDeleteDocModal: function () {
-      Actions.hideDeleteDocModal();
-    },
-
-    deleteDoc: function () {
-      Actions.hideDeleteDocModal();
-      Actions.deleteDoc(this.state.doc);
-    },
-
-    getEditor: function () {
-      return (this.refs.docEditor) ? this.refs.docEditor.getEditor() : null;
-    },
-
-    checkDocIsValid: function () {
-      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: function () {
-      this.refs.docEditor.clearChanges();
-    },
-
-    getExtensionIcons: function () {
-      var extensions = FauxtonAPI.getExtensions('DocEditor:icons');
-      return _.map(extensions, function (Extension, i) {
-        return (<Extension doc={this.state.doc} key={i} database={this.props.database} />);
-      }, this);
-    },
-
-    getButtonRow: function () {
-      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>
-      );
-    },
+    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="docEditor"
+        defaultCode={code}
+        mode="json"
+        autoFocus={true}
+        editorCommands={editorCommands}
+        notifyUnsavedChanges={true}
+        stringEditModalEnabled={true} />
+    );
+  },
+
+  componentDidMount: function () {
+    store.on('change', this.onChange, this);
+  },
+
+  componentWillUnmount: function () {
+    store.off('change', this.onChange);
+  },
+
+  // whenever a file is uploaded, reset the editor
+  componentWillUpdate: function (nextProps, nextState) {
+    if (this.state.numFilesUploaded !== nextState.numFilesUploaded) {
+      this.getEditor().setValue(JSON.stringify(nextState.doc.attributes, null, '  '));
+    }
+  },
 
-    render: function () {
-      var saveButtonLabel = (this.props.isNewDoc) ? 'Create Document' : 'Save Changes';
+  onChange: function () {
+    if (this.isMounted()) {
+      this.setState(this.getStoreState());
+    }
+  },
 
-      return (
-        <div>
-          <div id="doc-editor-actions-panel">
-            <div className="doc-actions-left">
-              <button className="save-doc btn btn-success save" type="button" onClick={this.saveDoc}>
-                <i className="icon fonticon-ok-circled"></i> {saveButtonLabel}
-              </button>
-              <div>
-                <a href={this.props.previousPage} className="js-back cancel-button">Cancel</a>
-              </div>
-            </div>
-            <div className="alignRight">
-              {this.getButtonRow()}
-            </div>
-          </div>
+  saveDoc: function () {
+    Actions.saveDoc(this.state.doc, this.checkDocIsValid(), this.onSaveComplete);
+  },
+
+  onSaveComplete: function (json) {
+    this.getEditor().clearChanges();
+  },
+
+  hideDeleteDocModal: function () {
+    Actions.hideDeleteDocModal();
+  },
+
+  deleteDoc: function () {
+    Actions.hideDeleteDocModal();
+    Actions.deleteDoc(this.state.doc);
+  },
 
-          <div className="code-region">
-            <div className="bgEditorGutter"></div>
-            <div id="editor-container" className="doc-code">{this.getCodeEditor()}</div>
+  getEditor: function () {
+    return (this.refs.docEditor) ? this.refs.docEditor.getEditor() : null;
+  },
 
+  checkDocIsValid: function () {
+    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: function () {
+    this.refs.docEditor.clearChanges();
+  },
+
+  getExtensionIcons: function () {
+    var extensions = FauxtonAPI.getExtensions('DocEditor:icons');
+    return _.map(extensions, function (Extension, i) {
+      return (<Extension doc={this.state.doc} key={i} database={this.props.database} />);
+    }, this);
+  },
+
+  getButtonRow: function () {
+    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>
+    );
+  },
+
+  render: function () {
+    var saveButtonLabel = (this.props.isNewDoc) ? 'Create Document' : 'Save Changes';
+
+    return (
+      <div>
+        <div id="doc-editor-actions-panel">
+          <div className="doc-actions-left">
+            <button className="save-doc btn btn-success save" type="button" onClick={this.saveDoc}>
+              <i className="icon fonticon-ok-circled"></i> {saveButtonLabel}
+            </button>
+            <div>
+              <a href={this.props.previousPage} 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>
 
-          <UploadModal
-            ref="uploadModal"
-            visible={this.state.uploadModalVisible}
-            doc={this.state.doc} />
-          <CloneDocModal
-            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>
-      );
-    }
-  });
-
-  var AttachmentsPanelButton = React.createClass({
-
-    propTypes: {
-      isLoading: React.PropTypes.bool.isRequired,
-      doc: React.PropTypes.object
-    },
-
-    getDefaultProps: function () {
-      return {
-        isLoading: true,
-        doc: {}
-      };
-    },
-
-    getAttachmentList: function () {
-      var db = this.props.doc.database.get('id');
-      var doc = this.props.doc.get('_id');
-
-      return _.map(this.props.doc.get('_attachments'), function (item, filename) {
-        var url = FauxtonAPI.urls('document', 'attachment', db, doc, app.utils.safeURLName(filename));
-        return (
-          <li key={filename}>
-            <a href={url} target="_blank" data-bypass="true"> <strong>{filename}</strong>
-              <span className="attachment-delimiter">-</span>
-              <span>{item.content_type}, {Helpers.formatSize(item.length)}</span>
-            </a>
-          </li>
-        );
-      });
-    },
-
-    render: function () {
-      if (this.props.isLoading || !this.props.doc.get('_attachments')) {
-        return false;
-      }
 
+        <UploadModal
+          ref="uploadModal"
+          visible={this.state.uploadModalVisible}
+          doc={this.state.doc} />
+        <CloneDocModal
+          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>
+    );
+  }
+});
+
+var AttachmentsPanelButton = React.createClass({
+
+  propTypes: {
+    isLoading: React.PropTypes.bool.isRequired,
+    doc: React.PropTypes.object
+  },
+
+  getDefaultProps: function () {
+    return {
+      isLoading: true,
+      doc: {}
+    };
+  },
+
+  getAttachmentList: function () {
+    var db = this.props.doc.database.get('id');
+    var doc = this.props.doc.get('_id');
+
+    return _.map(this.props.doc.get('_attachments'), function (item, filename) {
+      var url = FauxtonAPI.urls('document', 'attachment', db, doc, app.utils.safeURLName(filename));
       return (
-        <div className="panel-section view-attachments-section btn-group">
-          <button className="panel-button dropdown-toggle btn" data-bypass="true" data-toggle="dropdown" title="View Attachments"
-            id="view-attachments-menu">
-            <i className="icon icon-paper-clip"></i>
-            <span className="button-text">View Attachments</span>
-            <span className="caret"></span>
-          </button>
-          <ul className="dropdown-menu" role="menu" aria-labelledby="view-attachments-menu">
-            {this.getAttachmentList()}
-          </ul>
-        </div>
+        <li key={filename}>
+          <a href={url} target="_blank" data-bypass="true"> <strong>{filename}</strong>
+            <span className="attachment-delimiter">-</span>
+            <span>{item.content_type}, {Helpers.formatSize(item.length)}</span>
+          </a>
+        </li>
       );
+    });
+  },
+
+  render: function () {
+    if (this.props.isLoading || !this.props.doc.get('_attachments')) {
+      return false;
     }
-  });
-
-
-  var PanelButton = React.createClass({
-    propTypes: {
-      title: React.PropTypes.string.isRequired,
-      onClick: React.PropTypes.func.isRequired,
-      className: React.PropTypes.string
-    },
-
-    getDefaultProps: function () {
-      return {
-        title: '',
-        iconClass: '',
-        onClick: function () { },
-        className: ''
-      };
-    },
-
-    render: function () {
-      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>
-      );
+
+    return (
+      <div className="panel-section view-attachments-section btn-group">
+        <button className="panel-button dropdown-toggle btn" data-bypass="true" data-toggle="dropdown" title="View Attachments"
+          id="view-attachments-menu">
+          <i className="icon icon-paper-clip"></i>
+          <span className="button-text">View Attachments</span>
+          <span className="caret"></span>
+        </button>
+        <ul className="dropdown-menu" role="menu" aria-labelledby="view-attachments-menu">
+          {this.getAttachmentList()}
+        </ul>
+      </div>
+    );
+  }
+});
+
+
+var PanelButton = React.createClass({
+  propTypes: {
+    title: React.PropTypes.string.isRequired,
+    onClick: React.PropTypes.func.isRequired,
+    className: React.PropTypes.string
+  },
+
+  getDefaultProps: function () {
+    return {
+      title: '',
+      iconClass: '',
+      onClick: function () { },
+      className: ''
+    };
+  },
+
+  render: function () {
+    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>
+    );
+  }
+});
+
+
+var UploadModal = React.createClass({
+  propTypes: {
+    visible: React.PropTypes.bool.isRequired,
+    doc: React.PropTypes.object
+  },
+
+  getInitialState: function () {
+    return this.getStoreState();
+  },
+
+  getStoreState: function () {
+    return {
+      inProgress: store.isUploadInProgress(),
+      loadPercentage: store.getUploadLoadPercentage(),
+      errorMessage: store.getFileUploadErrorMsg()
+    };
+  },
+
+  closeModal: function (e) {
+    if (e) {
+      e.preventDefault();
     }
-  });
-
-
-  var UploadModal = React.createClass({
-    propTypes: {
-      visible: React.PropTypes.bool.isRequired,
-      doc: React.PropTypes.object
-    },
-
-    getInitialState: function () {
-      return this.getStoreState();
-    },
-
-    getStoreState: function () {
-      return {
-        inProgress: store.isUploadInProgress(),
-        loadPercentage: store.getUploadLoadPercentage(),
-        errorMessage: store.getFileUploadErrorMsg()
-      };
-    },
-
-    closeModal: function (e) {
-      if (e) {
-        e.preventDefault();
-      }
-
-      if (this.state.inProgress) {
-        Actions.cancelUpload();
-      }
-      Actions.hideUploadModal();
-      Actions.resetUploadModal();
-    },
-
-    upload: function () {
-      Actions.uploadAttachment({
-        doc: this.props.doc,
-        rev: this.props.doc.get('_rev'),
-        files: $(ReactDOM.findDOMNode(this.refs.attachments))[0].files
-      });
-    },
-
-    render: function () {
-      var errorClasses = 'alert alert-error';
-      if (this.state.errorMessage === '') {
-        errorClasses += ' hide';
-      }
-      var loadIndicatorClasses = 'progress progress-info';
-      if (!this.state.inProgress) {
-        loadIndicatorClasses += ' hide';
-      }
 
-      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="uploadForm" className="form" method="post">
-                <p>
-                  Please select the file you want to upload as an attachment to this document. This creates a new
-                  revision of the document, so it's not necessary to save after uploading.
-                </p>
-                <input ref="attachments" type="file" name="_attachments" />
-                <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-success save" onClick={this.upload}>
-              Upload Attachment
-            </button>
-          </Modal.Footer>
-        </Modal>
-      );
+    if (this.state.inProgress) {
+      Actions.cancelUpload();
+    }
+    Actions.hideUploadModal();
+    Actions.resetUploadModal();
+  },
+
+  upload: function () {
+    Actions.uploadAttachment({
+      doc: this.props.doc,
+      rev: this.props.doc.get('_rev'),
+      files: $(ReactDOM.findDOMNode(this.refs.attachments))[0].files
+    });
+  },
+
+  render: function () {
+    var errorClasses = 'alert alert-error';
+    if (this.state.errorMessage === '') {
+      errorClasses += ' hide';
+    }
+    var loadIndicatorClasses = 'progress progress-info';
+    if (!this.state.inProgress) {
+      loadIndicatorClasses += ' hide';
     }
-  });
-
-
-  var CloneDocModal = React.createClass({
-    propTypes: {
-      visible: React.PropTypes.bool.isRequired
-    },
-
-    getInitialState: function () {
-      return {
-        uuid: null
-      };
-    },
-
-    cloneDoc: function () {
-      if (this.props.onSubmit) {
-        this.props.onSubmit();
-      }
-      Actions.cloneDoc(this.state.uuid);
-    },
-
-    componentDidUpdate: function () {
-      if (this.state.uuid === null) {
-        var uuid = new FauxtonAPI.UUID();
-        uuid.fetch().then(function () {
-          this.setState({ uuid: uuid.next() });
-        }.bind(this));
-      }
-    },
-
-    closeModal: function (e) {
-      if (e) {
-        e.preventDefault();
-      }
-      Actions.hideCloneDocModal();
-    },
-
-    docIDChange: function (e) {
-      this.setState({ uuid: e.target.value });
-    },
-
-    render: function () {
-      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" method="post">
+    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="uploadForm" className="form" method="post">
               <p>
-                Set new document's ID:
+                Please select the file you want to upload as an attachment to this document. This creates a new
+                revision of the document, so it's not necessary to save after uploading.
               </p>
-              <input ref="newDocId" type="text" autoFocus={true} className="input-block-level"
-                onChange={this.docIDChange} value={this.state.uuid} />
+              <input ref="attachments" type="file" name="_attachments" />
+              <br />
             </form>
-          </Modal.Body>
-          <Modal.Footer>
-            <a href="#" data-bypass="true" className="cancel-link" onClick={this.closeModal}>Cancel</a>
-            <button className="btn btn-success save" onClick={this.cloneDoc}>
-              <i className="fonticon-ok-circled"></i> Clone Document
-            </button>
-          </Modal.Footer>
-        </Modal>
-      );
+
+            <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-success save" onClick={this.upload}>
+            Upload Attachment
+          </button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+});
+
+
+var CloneDocModal = React.createClass({
+  propTypes: {
+    visible: React.PropTypes.bool.isRequired
+  },
+
+  getInitialState: function () {
+    return {
+      uuid: null
+    };
+  },
+
+  cloneDoc: function () {
+    if (this.props.onSubmit) {
+      this.props.onSubmit();
+    }
+    Actions.cloneDoc(this.state.uuid);
+  },
+
+  componentDidUpdate: function () {
+    if (this.state.uuid === null) {
+      var uuid = new FauxtonAPI.UUID();
+      uuid.fetch().then(function () {
+        this.setState({ uuid: uuid.next() });
+      }.bind(this));
     }
-  });
+  },
 
+  closeModal: function (e) {
+    if (e) {
+      e.preventDefault();
+    }
+    Actions.hideCloneDocModal();
+  },
 
-  return {
-    DocEditorController: DocEditorController,
-    AttachmentsPanelButton: AttachmentsPanelButton,
-    UploadModal: UploadModal,
-    CloneDocModal: CloneDocModal
-  };
+  docIDChange: function (e) {
+    this.setState({ uuid: e.target.value });
+  },
 
+  render: function () {
+    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" method="post">
+            <p>
+              Set new document's ID:
+            </p>
+            <input ref="newDocId" 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-success save" onClick={this.cloneDoc}>
+            <i className="fonticon-ok-circled"></i> Clone Document
+          </button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
 });
+
+
+export default {
+  DocEditorController: DocEditorController,
+  AttachmentsPanelButton: AttachmentsPanelButton,
+  UploadModal: UploadModal,
+  CloneDocModal: CloneDocModal
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/doc-editor/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/doc-editor/stores.js b/app/addons/documents/doc-editor/stores.js
index 2635ce5..a553de3 100644
--- a/app/addons/documents/doc-editor/stores.js
+++ b/app/addons/documents/doc-editor/stores.js
@@ -10,201 +10,196 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../../core/api',
-  './actiontypes'
-],
-
-function (FauxtonAPI, 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
-      }
+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);
+Stores.docEditorStore = new Stores.DocEditorStore();
+Stores.docEditorStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.docEditorStore.dispatch);
 
-  return Stores;
-});
+export default Stores;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/doc-editor/tests/doc-editor.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/doc-editor/tests/doc-editor.componentsSpec.react.jsx b/app/addons/documents/doc-editor/tests/doc-editor.componentsSpec.react.jsx
index 8dfd2ec..2a9b374 100644
--- a/app/addons/documents/doc-editor/tests/doc-editor.componentsSpec.react.jsx
+++ b/app/addons/documents/doc-editor/tests/doc-editor.componentsSpec.react.jsx
@@ -10,233 +10,227 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../../../app',
-  '../../../../core/api',
-  'react',
-  'react-dom',
-  '../../resources',
-  '../components.react',
-  '../stores',
-  '../actions',
-  '../actiontypes',
-  '../../../databases/base',
-  '../../../../../test/mocha/testUtils',
-  'react-bootstrap',
-  'react-addons-test-utils',
-  'sinon'
-], function (app, FauxtonAPI, React, ReactDOM, Documents, Components, Stores, Actions, ActionTypes, Databases, utils,
-  ReactBoostrap, TestUtils, sinon) {
-
-  var assert = utils.assert;
-  var docJSON = {
-    _id: '_design/test-doc',
-    views: {
-      'test-view': {
-        map: 'function () {};'
-      }
+import app from "../../../../app";
+import FauxtonAPI from "../../../../core/api";
+import React from "react";
+import ReactDOM from "react-dom";
+import Documents from "../../resources";
+import Components from "../components.react";
+import Stores from "../stores";
+import Actions from "../actions";
+import ActionTypes from "../actiontypes";
+import Databases from "../../../databases/base";
+import utils from "../../../../../test/mocha/testUtils";
+import TestUtils from "react-addons-test-utils";
+import sinon from "sinon";
+
+var assert = utils.assert;
+var docJSON = {
+  _id: '_design/test-doc',
+  views: {
+    'test-view': {
+      map: 'function () {};'
     }
-  };
-
-  var docWithAttachmentsJSON = {
-    _id: '_design/test-doc',
-    _rev: '12345',
-    blah: {
-      whatever: {
-        something: 1
-      }
+  }
+};
+
+var docWithAttachmentsJSON = {
+  _id: '_design/test-doc',
+  _rev: '12345',
+  blah: {
+    whatever: {
+      something: 1
+    }
+  },
+  _attachments: {
+    "one.png": {
+      "content-type": "images/png",
+      "length": 100
     },
-    _attachments: {
-      "one.png": {
-        "content-type": "images/png",
-        "length": 100
-      },
-      "one.zip": {
-        "content-type": "application/zip",
-        "length": 111100
-      }
+    "one.zip": {
+      "content-type": "application/zip",
+      "length": 111100
     }
-  };
+  }
+};
 
-  var database = new Databases.Model({ id: 'id' });
+var database = new Databases.Model({ id: 'id' });
 
 
-  describe('DocEditorController', function () {
-    var container;
+describe('DocEditorController', function () {
+  var container;
 
-    beforeEach(function () {
-      container = document.createElement('div');
-    });
+  beforeEach(function () {
+    container = document.createElement('div');
+  });
 
-    afterEach(function () {
-      ReactDOM.unmountComponentAtNode(container);
-    });
+  afterEach(function () {
+    ReactDOM.unmountComponentAtNode(container);
+  });
 
-    it('loading indicator appears on load', function () {
-      var el = TestUtils.renderIntoDocument(<Components.DocEditorController />, container);
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.loading-lines').length, 1);
-    });
+  it('loading indicator appears on load', function () {
+    var el = TestUtils.renderIntoDocument(<Components.DocEditorController />, container);
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.loading-lines').length, 1);
+  });
+
+  it('new docs do not show the button row', function () {
+    var el = TestUtils.renderIntoDocument(<Components.DocEditorController isNewDoc={true} database={database} />, container);
 
-    it('new docs do not show the button row', function () {
-      var el = TestUtils.renderIntoDocument(<Components.DocEditorController isNewDoc={true} database={database} />, container);
-
-      var doc = new Documents.Doc(docJSON, { database: database });
-      FauxtonAPI.dispatch({
-        type: ActionTypes.DOC_LOADED,
-        options: {
-          doc: doc
-        }
-      });
-
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.loading-lines').length, 0);
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.icon-circle-arrow-up').length, 0);
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.icon-repeat').length, 0);
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.icon-trash').length, 0);
+    var doc = new Documents.Doc(docJSON, { database: database });
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DOC_LOADED,
+      options: {
+        doc: doc
+      }
     });
 
-    it('view attachments button does not appear with no attachments', function () {
-      var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
-
-      var doc = new Documents.Doc(docJSON, { database: database });
-      FauxtonAPI.dispatch({
-        type: ActionTypes.DOC_LOADED,
-        options: {
-          doc: doc
-        }
-      });
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.view-attachments-section').length, 0);
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.loading-lines').length, 0);
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.icon-circle-arrow-up').length, 0);
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.icon-repeat').length, 0);
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.icon-trash').length, 0);
+  });
+
+  it('view attachments button does not appear with no attachments', function () {
+    var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
+
+    var doc = new Documents.Doc(docJSON, { database: database });
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DOC_LOADED,
+      options: {
+        doc: doc
+      }
     });
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.view-attachments-section').length, 0);
+  });
 
-    it('view attachments button shows up when the doc has attachments', function () {
-      var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
-
-      var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-      FauxtonAPI.dispatch({
-        type: ActionTypes.DOC_LOADED,
-        options: {
-          doc: doc
-        }
-      });
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.view-attachments-section').length, 1);
+  it('view attachments button shows up when the doc has attachments', function () {
+    var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
+
+    var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DOC_LOADED,
+      options: {
+        doc: doc
+      }
     });
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.view-attachments-section').length, 1);
+  });
 
-    it('view attachments dropdown contains right number of docs', function () {
-      var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
-
-      var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-      FauxtonAPI.dispatch({
-        type: ActionTypes.DOC_LOADED,
-        options: {
-          doc: doc
-        }
-      });
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.view-attachments-section .dropdown-menu li').length, 2);
+  it('view attachments dropdown contains right number of docs', function () {
+    var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
+
+    var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DOC_LOADED,
+      options: {
+        doc: doc
+      }
     });
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.view-attachments-section .dropdown-menu li').length, 2);
+  });
 
-    //issues with this test running with all other tests. It passes on its own
-    it('view attachments dropdown contains correct urls', function () {
-      var el = TestUtils.renderIntoDocument(
-        <Components.DocEditorController database={database} />, container
-      );
+  //issues with this test running with all other tests. It passes on its own
+  it('view attachments dropdown contains correct urls', function () {
+    var el = TestUtils.renderIntoDocument(
+      <Components.DocEditorController database={database} />, container
+    );
+
+    var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DOC_LOADED,
+      options: {
+        doc: doc
+      }
+    });
 
-      var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-      FauxtonAPI.dispatch({
-        type: ActionTypes.DOC_LOADED,
-        options: {
-          doc: doc
-        }
-      });
+    var $attachmentNode = $(ReactDOM.findDOMNode(el)).find('.view-attachments-section .dropdown-menu li');
+    var attachmentURLactual = $attachmentNode.find('a').attr('href');
 
-      var $attachmentNode = $(ReactDOM.findDOMNode(el)).find('.view-attachments-section .dropdown-menu li');
-      var attachmentURLactual = $attachmentNode.find('a').attr('href');
+    assert.equal(attachmentURLactual, '../../id/_design/test-doc/one.png');
+  });
 
-      assert.equal(attachmentURLactual, '../../id/_design/test-doc/one.png');
+  it('setting deleteDocModal=true in store shows modal', function () {
+    var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
+    var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DOC_LOADED,
+      options: {
+        doc: doc
+      }
     });
 
-    it('setting deleteDocModal=true in store shows modal', function () {
-      var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
-      var 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);
 
-      // 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();
 
-      Actions.showDeleteDocModal();
+    var modalContent = $('body').find('.confirmation-modal .modal-body p')[0];
+    assert.ok(/Are you sure you want to delete this document\?/.test(modalContent.innerHTML));
+  });
 
-      var modalContent = $('body').find('.confirmation-modal .modal-body p')[0];
-      assert.ok(/Are you sure you want to delete this document\?/.test(modalContent.innerHTML));
+  it('setting uploadDocModal=true in store shows modal', function () {
+    var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
+    var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DOC_LOADED,
+      options: {
+        doc: doc
+      }
     });
 
-    it('setting uploadDocModal=true in store shows modal', function () {
-      var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
-      var doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-      FauxtonAPI.dispatch({
-        type: ActionTypes.DOC_LOADED,
-        options: {
-          doc: doc
-        }
-      });
-
-      assert.equal($('body').find('.upload-file-modal').length, 0);
-      Actions.showUploadModal();
-      assert.notEqual($('body').find('.upload-file-modal').length, 0);
-    });
+    assert.equal($('body').find('.upload-file-modal').length, 0);
+    Actions.showUploadModal();
+    assert.notEqual($('body').find('.upload-file-modal').length, 0);
   });
+});
 
 
-  describe("AttachmentsPanelButton", function () {
-    var container, doc;
+describe("AttachmentsPanelButton", function () {
+  var container, doc;
 
-    beforeEach(function () {
-      doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
-      container = document.createElement('div');
-    });
+  beforeEach(function () {
+    doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
+    container = document.createElement('div');
+  });
 
-    afterEach(function () {
-      ReactDOM.unmountComponentAtNode(container);
-    });
+  afterEach(function () {
+    ReactDOM.unmountComponentAtNode(container);
+  });
 
-    it('does not show up when loading', function () {
-      var el = TestUtils.renderIntoDocument(<Components.AttachmentsPanelButton isLoading={true} doc={doc} />, container);
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.panel-button').length, 0);
-    });
+  it('does not show up when loading', function () {
+    var el = TestUtils.renderIntoDocument(<Components.AttachmentsPanelButton isLoading={true} doc={doc} />, container);
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.panel-button').length, 0);
+  });
 
-    it('shows up after loading', function () {
-      var el = TestUtils.renderIntoDocument(<Components.AttachmentsPanelButton isLoading={false} doc={doc} />, container);
-      assert.equal($(ReactDOM.findDOMNode(el)).find('.panel-button').length, 1);
-    });
+  it('shows up after loading', function () {
+    var el = TestUtils.renderIntoDocument(<Components.AttachmentsPanelButton isLoading={false} doc={doc} />, container);
+    assert.equal($(ReactDOM.findDOMNode(el)).find('.panel-button').length, 1);
   });
+});
 
 
-  describe("Custom Extension Buttons", function () {
-    it('supports buttons', function () {
-      var CustomButton = React.createClass({
-        render: function () {
-          return (
-            <div>
-              <button>Oh no she di'n't!</button>
-              <span id="testDatabaseName">{this.props.database.id}</span>
-            </div>
-          );
-        }
-      });
-      FauxtonAPI.registerExtension('DocEditor:icons', CustomButton);
-
-      var container = document.createElement('div');
-      var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
-      assert.isTrue(/Oh\sno\sshe\sdi'n't!/.test(ReactDOM.findDOMNode(el).outerHTML));
-
-      // confirm the database name was also included
-      assert.equal($(ReactDOM.findDOMNode(el)).find("#testDatabaseName").html(), database.id);
-
-      ReactDOM.unmountComponentAtNode(container);
+describe("Custom Extension Buttons", function () {
+  it('supports buttons', function () {
+    var CustomButton = React.createClass({
+      render: function () {
+        return (
+          <div>
+            <button>Oh no she di'n't!</button>
+            <span id="testDatabaseName">{this.props.database.id}</span>
+          </div>
+        );
+      }
     });
-  });
+    FauxtonAPI.registerExtension('DocEditor:icons', CustomButton);
+
+    var container = document.createElement('div');
+    var el = TestUtils.renderIntoDocument(<Components.DocEditorController database={database} />, container);
+    assert.isTrue(/Oh\sno\sshe\sdi'n't!/.test(ReactDOM.findDOMNode(el).outerHTML));
 
+    // confirm the database name was also included
+    assert.equal($(ReactDOM.findDOMNode(el)).find("#testDatabaseName").html(), database.id);
+
+    ReactDOM.unmountComponentAtNode(container);
+  });
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/doc-editor/tests/doc-editor.storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/doc-editor/tests/doc-editor.storesSpec.js b/app/addons/documents/doc-editor/tests/doc-editor.storesSpec.js
index 69b790c..fb5480b 100644
--- a/app/addons/documents/doc-editor/tests/doc-editor.storesSpec.js
+++ b/app/addons/documents/doc-editor/tests/doc-editor.storesSpec.js
@@ -10,75 +10,70 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../../../app',
-  '../../../../core/api',
-  '../stores',
+import app from "../../../../app";
+import FauxtonAPI from "../../../../core/api";
+import Stores from "../stores";
+import Documents from "../../resources";
+import utils from "../../../../../test/mocha/testUtils";
+FauxtonAPI.router = new FauxtonAPI.Router([]);
 
-  '../../resources',
-  '../../../../../test/mocha/testUtils',
-], function (app, FauxtonAPI, Stores, Documents, utils) {
-  FauxtonAPI.router = new FauxtonAPI.Router([]);
+const assert = utils.assert;
+const store = Stores.docEditorStore;
 
-  const assert = utils.assert;
-  const store = Stores.docEditorStore;
+const doc = new Documents.Doc({id: 'foo'}, {database: 'bar'});
 
-  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);
-    });
+describe('DocEditorStore', function () {
+  afterEach(function () {
+    store.reset();
+  });
 
-    it('docLoaded() marks loading as complete', function () {
-      store.docLoaded({ doc: doc });
-      assert.equal(store.isLoading(), false);
-    });
+  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('showCloneDocModal / hideCloneDocModal', function () {
-      store.showCloneDocModal();
-      assert.equal(store.isCloneDocModalVisible(), true);
-      store.hideCloneDocModal();
-      assert.equal(store.isCloneDocModalVisible(), false);
-    });
+  it('docLoaded() marks loading as complete', function () {
+    store.docLoaded({ doc: doc });
+    assert.equal(store.isLoading(), false);
+  });
 
-    it('showDeleteDocModal / hideCloneDocModal', function () {
-      store.showDeleteDocModal();
-      assert.equal(store.isDeleteDocModalVisible(), true);
-      store.hideDeleteDocModal();
-      assert.equal(store.isDeleteDocModalVisible(), false);
-    });
+  it('showCloneDocModal / hideCloneDocModal', function () {
+    store.showCloneDocModal();
+    assert.equal(store.isCloneDocModalVisible(), true);
+    store.hideCloneDocModal();
+    assert.equal(store.isCloneDocModalVisible(), false);
+  });
 
-    it('showCloneDocModal / hideCloneDocModal', function () {
-      store.showUploadModal();
-      assert.equal(store.isUploadModalVisible(), true);
-      store.hideUploadModal();
-      assert.equal(store.isUploadModalVisible(), false);
-    });
+  it('showDeleteDocModal / hideCloneDocModal', function () {
+    store.showDeleteDocModal();
+    assert.equal(store.isDeleteDocModalVisible(), true);
+    store.hideDeleteDocModal();
+    assert.equal(store.isDeleteDocModalVisible(), false);
+  });
 
-    it('reset() resets all values', function () {
-      store.docLoaded({ doc: doc });
-      store.showCloneDocModal();
-      store.showDeleteDocModal();
-      store.showUploadModal();
+  it('showCloneDocModal / hideCloneDocModal', function () {
+    store.showUploadModal();
+    assert.equal(store.isUploadModalVisible(), true);
+    store.hideUploadModal();
+    assert.equal(store.isUploadModalVisible(), false);
+  });
 
-      store.reset();
-      assert.equal(store.isLoading(), true);
-      assert.equal(store.isCloneDocModalVisible(), false);
-      assert.equal(store.isDeleteDocModalVisible(), false);
-      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);
   });
 
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/header/header.actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/header/header.actions.js b/app/addons/documents/header/header.actions.js
index 282d20b..3dc26b3 100644
--- a/app/addons/documents/header/header.actions.js
+++ b/app/addons/documents/header/header.actions.js
@@ -10,41 +10,36 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../../app',
-  '../../../core/api',
-  './header.actiontypes',
-  '../queryoptions/actions',
-
-],
-function (app, FauxtonAPI, ActionTypes, ActionsQueryOptions) {
-
-  return {
-
-    toggleIncludeDocs: function (state, bulkDocsCollection) {
-      var params = app.getParams();
-
-      if (state) {
-        delete params.include_docs;
-        delete params.conflicts;
-      } else {
-        params.include_docs = true;
-        params.conflicts = true;
-      }
+import app from "../../../app";
+import FauxtonAPI from "../../../core/api";
+import ActionTypes from "./header.actiontypes";
+import ActionsQueryOptions from "../queryoptions/actions";
+
+export default {
+
+  toggleIncludeDocs: function (state, bulkDocsCollection) {
+    var params = app.getParams();
+
+    if (state) {
+      delete params.include_docs;
+      delete params.conflicts;
+    } else {
+      params.include_docs = true;
+      params.conflicts = true;
+    }
 
-      app.utils.localStorageSet('include_docs_bulkdocs', bulkDocsCollection.toJSON());
+    app.utils.localStorageSet('include_docs_bulkdocs', bulkDocsCollection.toJSON());
 
-      ActionsQueryOptions.runQuery(params);
-    },
+    ActionsQueryOptions.runQuery(params);
+  },
 
-    toggleTableView: function (state) {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.TOGGLE_TABLEVIEW,
-        options: {
-          enable: state
-        }
-      });
-    }
+  toggleTableView: function (state) {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.TOGGLE_TABLEVIEW,
+      options: {
+        enable: state
+      }
+    });
+  }
 
-  };
-});
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/header/header.actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/header/header.actiontypes.js b/app/addons/documents/header/header.actiontypes.js
index f4de5fc..b7fc6a6 100644
--- a/app/addons/documents/header/header.actiontypes.js
+++ b/app/addons/documents/header/header.actiontypes.js
@@ -10,8 +10,6 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([], function () {
-  return {
-    TOGGLE_TABLEVIEW: 'TOGGLE_TABLEVIEW',
-  };
-});
+export default {
+  TOGGLE_TABLEVIEW: 'TOGGLE_TABLEVIEW',
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/header/header.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/header/header.react.jsx b/app/addons/documents/header/header.react.jsx
index abeed0b..165bc0d 100644
--- a/app/addons/documents/header/header.react.jsx
+++ b/app/addons/documents/header/header.react.jsx
@@ -10,105 +10,92 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../../app',
-  '../../../core/api',
-  'react',
-  './header.actions',
-  '../../components/react-components.react',
-
-  '../index-results/stores',
-  '../index-results/actions',
-  'react-bootstrap',
-  '../queryoptions/stores',
-  'react-addons-css-transition-group'
-],
-
-function (app, FauxtonAPI, React, Actions, ReactComponents,
-  IndexResultsStore, IndexResultsActions, ReactBootstrap, QueryOptionsStore, ReactCSSTransitionGroup) {
-
-  var indexResultsStore = IndexResultsStore.indexResultsStore;
-  var queryOptionsStore = QueryOptionsStore.queryOptionsStore;
-
-
-  var ToggleHeaderButton = ReactComponents.ToggleHeaderButton;
-
-  var ButtonGroup = ReactBootstrap.ButtonGroup;
-  var Button = ReactBootstrap.Button;
-
-
-  var BulkDocumentHeaderController = React.createClass({
-    getStoreState: function () {
-      return {
-        selectedView: indexResultsStore.getCurrentViewType(),
-        isTableView: indexResultsStore.getIsTableView(),
-        includeDocs: queryOptionsStore.getIncludeDocsEnabled(),
-        bulkDocCollection: indexResultsStore.getBulkDocCollection()
-      };
-    },
-
-    getInitialState: function () {
-      return this.getStoreState();
-    },
-
-    componentDidMount: function () {
-      indexResultsStore.on('change', this.onChange, this);
-      queryOptionsStore.on('change', this.onChange, this);
-
-    },
-
-    componentWillUnmount: function () {
-      indexResultsStore.off('change', this.onChange);
-      queryOptionsStore.off('change', this.onChange);
-    },
-
-    onChange: function () {
-      this.setState(this.getStoreState());
-    },
-
-    render: function () {
-      var isTableViewSelected = this.state.isTableView;
-
-      return (
-        <div className="alternative-header">
-          <ButtonGroup className="two-sides-toggle-button">
-            <Button
-              className={isTableViewSelected ? '' : 'active'}
-              onClick={this.toggleTableView.bind(this, false)}
-            >
-              <i className="fonticon-json" /> JSON
-            </Button>
-            <Button
-              className={isTableViewSelected ? 'active' : ''}
-              onClick={this.toggleTableView.bind(this, true)}
-            >
-              <i className="fonticon-table" /> Table
-            </Button>
-          </ButtonGroup>
-          {this.props.showIncludeAllDocs ? <ToggleHeaderButton
-            toggleCallback={this.toggleIncludeDocs}
-            containerClasses="header-control-box control-toggle-include-docs"
-            title="Enable/Disable include_docs"
-            fonticon={this.state.includeDocs ? 'icon-check' : 'icon-check-empty'}
-            iconDefaultClass="icon fontawesome"
-            text="" /> : null}  { /* text is set via responsive css */}
-        </div>
-      );
-    },
-
-    toggleIncludeDocs: function () {
-      Actions.toggleIncludeDocs(this.state.includeDocs, this.state.bulkDocCollection);
-    },
-
-    toggleTableView: function (enable) {
-      Actions.toggleTableView(enable);
-    }
-  });
-
-  var Views = {
-    BulkDocumentHeaderController: BulkDocumentHeaderController
-  };
+import app from "../../../app";
+import FauxtonAPI from "../../../core/api";
+import React from "react";
+import Actions from "./header.actions";
+import ReactComponents from "../../components/react-components.react";
+import IndexResultsStore from "../index-results/stores";
+import IndexResultsActions from "../index-results/actions";
+import { ButtonGroup, Button } from "react-bootstrap";
+import QueryOptionsStore from "../queryoptions/stores";
+import ReactCSSTransitionGroup from "react-addons-css-transition-group";
+
+var indexResultsStore = IndexResultsStore.indexResultsStore;
+var queryOptionsStore = QueryOptionsStore.queryOptionsStore;
+
+var ToggleHeaderButton = ReactComponents.ToggleHeaderButton;
+
+var BulkDocumentHeaderController = React.createClass({
+  getStoreState: function () {
+    return {
+      selectedView: indexResultsStore.getCurrentViewType(),
+      isTableView: indexResultsStore.getIsTableView(),
+      includeDocs: queryOptionsStore.getIncludeDocsEnabled(),
+      bulkDocCollection: indexResultsStore.getBulkDocCollection()
+    };
+  },
+
+  getInitialState: function () {
+    return this.getStoreState();
+  },
+
+  componentDidMount: function () {
+    indexResultsStore.on('change', this.onChange, this);
+    queryOptionsStore.on('change', this.onChange, this);
+
+  },
+
+  componentWillUnmount: function () {
+    indexResultsStore.off('change', this.onChange);
+    queryOptionsStore.off('change', this.onChange);
+  },
+
+  onChange: function () {
+    this.setState(this.getStoreState());
+  },
+
+  render: function () {
+    var isTableViewSelected = this.state.isTableView;
+
+    return (
+      <div className="alternative-header">
+        <ButtonGroup className="two-sides-toggle-button">
+          <Button
+            className={isTableViewSelected ? '' : 'active'}
+            onClick={this.toggleTableView.bind(this, false)}
+          >
+            <i className="fonticon-json" /> JSON
+          </Button>
+          <Button
+            className={isTableViewSelected ? 'active' : ''}
+            onClick={this.toggleTableView.bind(this, true)}
+          >
+            <i className="fonticon-table" /> Table
+          </Button>
+        </ButtonGroup>
+        {this.props.showIncludeAllDocs ? <ToggleHeaderButton
+          toggleCallback={this.toggleIncludeDocs}
+          containerClasses="header-control-box control-toggle-include-docs"
+          title="Enable/Disable include_docs"
+          fonticon={this.state.includeDocs ? 'icon-check' : 'icon-check-empty'}
+          iconDefaultClass="icon fontawesome"
+          text="" /> : null}  { /* text is set via responsive css */}
+      </div>
+    );
+  },
+
+  toggleIncludeDocs: function () {
+    Actions.toggleIncludeDocs(this.state.includeDocs, this.state.bulkDocCollection);
+  },
+
+  toggleTableView: function (enable) {
+    Actions.toggleTableView(enable);
+  }
+});
 
-  return Views;
+var Views = {
+  BulkDocumentHeaderController: BulkDocumentHeaderController
+};
 
-});
+export default Views;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/helpers.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js
index 87e3f74..e3862f8 100644
--- a/app/addons/documents/helpers.js
+++ b/app/addons/documents/helpers.js
@@ -10,116 +10,113 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../app',
-  '../../core/api'
-], function (app, FauxtonAPI) {
+import app from "../../app";
+import FauxtonAPI from "../../core/api";
 
 
-  function getPreviousPageForDoc (database, wasCloned) {
-    var previousPage = database.url('index'), // default to the current database's all_docs page
-        lastPages = FauxtonAPI.router.lastPages;
+function getPreviousPageForDoc (database, wasCloned) {
+  var previousPage = database.url('index'), // default to the current database's all_docs page
+      lastPages = FauxtonAPI.router.lastPages;
 
-    if (!wasCloned && lastPages.length >= 2) {
+  if (!wasCloned && lastPages.length >= 2) {
 
-      // if we came from "/new", we don't want to link the user there
-      if (/(new|new_view)$/.test(lastPages[1])) {
-        previousPage = lastPages[0];
-      } else {
-        previousPage = lastPages[1];
-      }
+    // if we came from "/new", we don't want to link the user there
+    if (/(new|new_view)$/.test(lastPages[1])) {
+      previousPage = lastPages[0];
+    } else {
+      previousPage = lastPages[1];
     }
-
-    return previousPage;
-  }
-
-  function getPreviousPage (database) {
-    return database.url('index');
   }
 
-  // sequence info is an array in couchdb2 with two indexes. On couch 1.x, it's just a string / number
-  function getSeqNum (val) {
-    return _.isArray(val) ? val[1] : val;
-  }
+  return previousPage;
+}
 
-  function getNewButtonLinks (databaseName) {
-    var addLinks = FauxtonAPI.getExtensions('sidebar:links');
-    var newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', databaseName);
-
-    var addNewLinks = _.reduce(addLinks, function (menuLinks, link) {
-      menuLinks.push({
-        title: link.title,
-        url: newUrlPrefix + '/' + link.url,
-        icon: 'fonticon-plus-circled'
-      });
-
-      return menuLinks;
-    }, [{
-      title: 'New Doc',
-      url: newUrlPrefix + '/new',
-      icon: 'fonticon-plus-circled'
-    }, {
-      title: 'New View',
-      url: newUrlPrefix + '/new_view',
-      icon: 'fonticon-plus-circled'
-    }, getMangoLink(databaseName)]);
+function getPreviousPage (database) {
+  return database.url('index');
+}
 
-    return [{
-      title: 'Add New',
-      links: addNewLinks
-    }];
-  }
+// sequence info is an array in couchdb2 with two indexes. On couch 1.x, it's just a string / number
+function getSeqNum (val) {
+  return _.isArray(val) ? val[1] : val;
+}
 
-  function getMangoLink (databaseName) {
-    var newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', databaseName);
+function getNewButtonLinks (databaseName) {
+  var addLinks = FauxtonAPI.getExtensions('sidebar:links');
+  var newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', databaseName);
 
-    return {
-      title: app.i18n.en_US['new-mango-index'],
-      url: newUrlPrefix + '/_index',
+  var addNewLinks = _.reduce(addLinks, function (menuLinks, link) {
+    menuLinks.push({
+      title: link.title,
+      url: newUrlPrefix + '/' + link.url,
       icon: 'fonticon-plus-circled'
-    };
-  }
+    });
+
+    return menuLinks;
+  }, [{
+    title: 'New Doc',
+    url: newUrlPrefix + '/new',
+    icon: 'fonticon-plus-circled'
+  }, {
+    title: 'New View',
+    url: newUrlPrefix + '/new_view',
+    icon: 'fonticon-plus-circled'
+  }, getMangoLink(databaseName)]);
+
+  return [{
+    title: 'Add New',
+    links: addNewLinks
+  }];
+}
+
+function getMangoLink (databaseName) {
+  var newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', databaseName);
 
-  function parseJSON (str) {
-    return JSON.parse('"' + str + '"');   // this ensures newlines are converted
-  }
-
-  function getModifyDatabaseLinks (databaseName, deleteCallback) {
-    return [{
-      title: 'Replicate Database',
-      icon: 'fonticon-replicate',
-      url: FauxtonAPI.urls('replication', 'app', databaseName)
-    }, {
-      title: 'Delete',
-      icon: 'fonticon-trash',
-      onClick: function () {
-        deleteCallback({showDeleteModal: true, dbId: databaseName});
-      }
-    }];
-  }
-
-  function truncateDoc (docString, maxRows) {
-    var lines = docString.split('\n');
-    var isTruncated = false;
-    if (lines.length > maxRows) {
-      isTruncated = true;
-      lines = lines.slice(0, maxRows);
-      docString = lines.join('\n');
+  return {
+    title: app.i18n.en_US['new-mango-index'],
+    url: newUrlPrefix + '/_index',
+    icon: 'fonticon-plus-circled'
+  };
+}
+
+function parseJSON (str) {
+  return JSON.parse('"' + str + '"');   // this ensures newlines are converted
+}
+
+function getModifyDatabaseLinks (databaseName, deleteCallback) {
+  return [{
+    title: 'Replicate Database',
+    icon: 'fonticon-replicate',
+    url: FauxtonAPI.urls('replication', 'app', databaseName)
+  }, {
+    title: 'Delete',
+    icon: 'fonticon-trash',
+    onClick: function () {
+      deleteCallback({showDeleteModal: true, dbId: databaseName});
     }
-    return {
-      isTruncated: isTruncated,
-      content: docString
-    };
+  }];
+}
+
+function truncateDoc (docString, maxRows) {
+  var lines = docString.split('\n');
+  var isTruncated = false;
+  if (lines.length > maxRows) {
+    isTruncated = true;
+    lines = lines.slice(0, maxRows);
+    docString = lines.join('\n');
   }
-
-
   return {
-    getPreviousPageForDoc: getPreviousPageForDoc,
-    getPreviousPage: getPreviousPage,
-    getSeqNum: getSeqNum,
-    getNewButtonLinks: getNewButtonLinks,
-    getModifyDatabaseLinks: getModifyDatabaseLinks,
-    parseJSON: parseJSON,
-    truncateDoc: truncateDoc
+    isTruncated: isTruncated,
+    content: docString
   };
-});
+}
+
+
+export default {
+  getPreviousPageForDoc: getPreviousPageForDoc,
+  getPreviousPage: getPreviousPage,
+  getSeqNum: getSeqNum,
+  getNewButtonLinks: getNewButtonLinks,
+  getModifyDatabaseLinks: getModifyDatabaseLinks,
+  parseJSON: parseJSON,
+  truncateDoc: truncateDoc
+};


Mime
View raw message