Antonio-Maranhao closed pull request #1126: Sidebar redux refactoring
URL: https://github.com/apache/couchdb-fauxton/pull/1126
 
 
   

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/__tests__/fetch-actions.test.js 
b/app/addons/documents/__tests__/fetch-actions.test.js
index ba85e5fca..ec7af92e5 100644
--- a/app/addons/documents/__tests__/fetch-actions.test.js
+++ b/app/addons/documents/__tests__/fetch-actions.test.js
@@ -296,7 +296,7 @@ describe('Docs Fetch API', () => {
 
       beforeEach(() => {
         notificationSpy = sinon.spy(FauxtonAPI, 'addNotification');
-        sidebarSpy = sinon.stub(SidebarActions, 'updateDesignDocs');
+        sidebarSpy = sinon.stub(SidebarActions, 'dispatchUpdateDesignDocs');
       });
 
       afterEach(() => {
@@ -325,7 +325,7 @@ describe('Docs Fetch API', () => {
         expect(sidebarSpy.calledOnce).toBe(false);
       });
 
-      it('calls updateDesignDocs when one of the deleted docs is a ddoc', () 
=> {
+      it('calls dispatchUpdateDesignDocs when one of the deleted docs is a 
ddoc', () => {
         const res = [
           {
             id: '_design/foo',
diff --git a/app/addons/documents/__tests__/results-toolbar.test.js 
b/app/addons/documents/__tests__/results-toolbar.test.js
index 255b87a73..c830b2cf1 100644
--- a/app/addons/documents/__tests__/results-toolbar.test.js
+++ b/app/addons/documents/__tests__/results-toolbar.test.js
@@ -25,7 +25,8 @@ describe('Results Toolbar', () => {
     hasSelectedItem: false,
     toggleSelectAll: () => {},
     isLoading: false,
-    queryOptionsParams: {}
+    queryOptionsParams: {},
+    databaseName: 'mydb'
   };
 
   beforeEach(() => {
diff --git a/app/addons/documents/components/results-toolbar.js 
b/app/addons/documents/components/results-toolbar.js
index e9f0b13bc..abaa037fa 100644
--- a/app/addons/documents/components/results-toolbar.js
+++ b/app/addons/documents/components/results-toolbar.js
@@ -13,12 +13,10 @@ import PropTypes from 'prop-types';
 
 import React from 'react';
 import BulkDocumentHeaderController from "../header/header";
-import Stores from "../sidebar/stores";
 import Components from "../../components/react-components";
 import Helpers from '../helpers';
 
 const {BulkActionComponent} = Components;
-const store = Stores.sidebarStore;
 
 export class ResultsToolBar extends React.Component {
   shouldComponentUpdate (nextProps) {
@@ -26,7 +24,6 @@ export class ResultsToolBar extends React.Component {
   }
 
   render () {
-    const database = store.getDatabase();
     const {
       hasResults,
       isListDeletable,
@@ -34,7 +31,8 @@ export class ResultsToolBar extends React.Component {
       allDocumentsSelected,
       hasSelectedItem,
       toggleSelectAll,
-      isLoading
+      isLoading,
+      databaseName
     } = this.props;
 
     // Determine if we need to display the bulk action selector
@@ -56,10 +54,10 @@ export class ResultsToolBar extends React.Component {
     }
 
     let createDocumentLink = null;
-    if (database) {
+    if (databaseName) {
       createDocumentLink = (
         <div className="document-result-screen__toolbar-flex-container">
-          <a href={Helpers.getNewDocUrl(database.id)} className="btn save 
document-result-screen__toolbar-create-btn btn-primary">
+          <a href={Helpers.getNewDocUrl(databaseName)} className="btn save 
document-result-screen__toolbar-create-btn btn-primary">
             Create Document
           </a>
         </div>
diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js
index 3c2a2ac63..a59d3a95b 100644
--- a/app/addons/documents/helpers.js
+++ b/app/addons/documents/helpers.js
@@ -102,20 +102,19 @@ const selectedViewContainsReduceFunction = (designDocs, 
selectedNavItem) => {
   let showReduce = false;
   // If a map/reduce view is selected, check if view contains reduce field
   if (designDocs && isViewSelected(selectedNavItem)) {
-    const ddocID = '_design/' + selectedNavItem.params.designDocName;
+    const ddocID = '_design/' + selectedNavItem.designDocName;
     const ddoc = designDocs.find(ddoc => ddoc._id === ddocID);
     showReduce = ddoc !== undefined && ddoc.views
-        && ddoc.views[selectedNavItem.params.indexName] !== undefined
-        && ddoc.views[selectedNavItem.params.indexName].reduce !== undefined;
+        && ddoc.views[selectedNavItem.indexName] !== undefined
+        && ddoc.views[selectedNavItem.indexName].reduce !== undefined;
   }
   return showReduce;
 };
 
 const isViewSelected = (selectedNavItem) => {
   return (selectedNavItem.navItem === 'designDoc'
-    && selectedNavItem.params
-    && selectedNavItem.params.designDocSection === 'Views'
-    && selectedNavItem.params.indexName);
+    && selectedNavItem.designDocSection === 'Views'
+    && selectedNavItem.indexName);
 };
 
 export default {
diff --git a/app/addons/documents/index-editor/__tests__/actions.test.js 
b/app/addons/documents/index-editor/__tests__/actions.test.js
index e55de6df8..cba84f556 100644
--- a/app/addons/documents/index-editor/__tests__/actions.test.js
+++ b/app/addons/documents/index-editor/__tests__/actions.test.js
@@ -27,6 +27,7 @@ describe('Index Editor Actions', function () {
   describe('delete view', function () {
     var designDocs, database, designDoc, designDocCollection, designDocId, 
viewName;
     beforeEach(function () {
+      FauxtonAPI.reduxDispatch = sinon.stub();
       database = {
         safeID: function () { return 'safeid';}
       };
diff --git a/app/addons/documents/index-editor/actions.js 
b/app/addons/documents/index-editor/actions.js
index 350ea6c0f..22b9a9b81 100644
--- a/app/addons/documents/index-editor/actions.js
+++ b/app/addons/documents/index-editor/actions.js
@@ -15,7 +15,6 @@ import FauxtonAPI from "../../../core/api";
 import Documents from "../resources";
 import ActionTypes from "./actiontypes";
 import SidebarActions from "../sidebar/actions";
-import SidebarActionTypes from "../sidebar/actiontypes";
 
 function selectReduceChanged (reduceOption) {
   FauxtonAPI.dispatch({
@@ -94,7 +93,7 @@ function saveView (viewInfo) {
       var oldDesignDoc = findDesignDoc(viewInfo.designDocs, 
viewInfo.originalDesignDocName);
       safeDeleteIndex(oldDesignDoc, viewInfo.designDocs, 'views', 
viewInfo.originalViewName, {
         onSuccess: function () {
-          SidebarActions.updateDesignDocs(viewInfo.designDocs);
+          SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
         }
       });
     }
@@ -102,7 +101,7 @@ function saveView (viewInfo) {
     if (viewInfo.designDocId === 'new-doc') {
       addDesignDoc(designDoc);
     }
-
+    SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
     FauxtonAPI.dispatch({ type: ActionTypes.VIEW_SAVED });
     var fragment = FauxtonAPI.urls('view', 'showView', 
viewInfo.database.safeID(), designDoc.safeID(), 
app.utils.safeURLName(viewInfo.viewName));
     FauxtonAPI.navigate(fragment, { trigger: true });
@@ -132,7 +131,7 @@ function deleteView (options) {
       FauxtonAPI.navigate(url);
     }
 
-    SidebarActions.updateDesignDocs(options.designDocs);
+    SidebarActions.dispatchUpdateDesignDocs(options.designDocs);
 
     FauxtonAPI.addNotification({
       msg: 'The <code>' + _.escape(options.indexName) + '</code> view has been 
deleted.',
@@ -140,7 +139,7 @@ function deleteView (options) {
       escape: false,
       clear: true
     });
-    FauxtonAPI.dispatch({ type: 
SidebarActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL });
+    SidebarActions.dispatchHideDeleteIndexModal();
   }
 
   return safeDeleteIndex(options.designDoc, options.designDocs, 'views', 
options.indexName, { onSuccess: onSuccess });
@@ -174,7 +173,7 @@ function cloneView (params) {
       type: 'success',
       clear: true
     });
-    SidebarActions.updateDesignDocs(params.designDocs);
+    SidebarActions.dispatchUpdateDesignDocs(params.designDocs);
   },
   function (xhr) {
     params.onComplete();
diff --git a/app/addons/documents/index-results/actions/fetch.js 
b/app/addons/documents/index-results/actions/fetch.js
index 2521a36b5..fc8df4eb6 100644
--- a/app/addons/documents/index-results/actions/fetch.js
+++ b/app/addons/documents/index-results/actions/fetch.js
@@ -197,6 +197,6 @@ export const processBulkDeleteResponse = (res, deletedDocs, 
designDocs, docType)
   }
 
   if (designDocs && hasDesignDocs) {
-    SidebarActions.updateDesignDocs(designDocs);
+    SidebarActions.dispatchUpdateDesignDocs(designDocs);
   }
 };
diff --git 
a/app/addons/documents/index-results/containers/QueryOptionsContainer.js 
b/app/addons/documents/index-results/containers/QueryOptionsContainer.js
index 00525773c..344e9e4b9 100644
--- a/app/addons/documents/index-results/containers/QueryOptionsContainer.js
+++ b/app/addons/documents/index-results/containers/QueryOptionsContainer.js
@@ -51,7 +51,7 @@ const mapStateToProps = ({indexResults, sidebar}, ownProps) 
=> {
   return {
     contentVisible: queryOptionsPanel.isVisible,
     includeDocs: queryOptionsPanel.includeDocs,
-    showReduce: showReduce(sidebar.designDocs, ownProps.selectedNavItem),
+    showReduce: showReduce(sidebar.designDocList, ownProps.selectedNavItem),
     reduce: queryOptionsPanel.reduce,
     groupLevel: queryOptionsPanel.groupLevel,
     showByKeys: queryOptionsPanel.showByKeys,
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index 85b761124..8a0c05ca9 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -94,12 +94,13 @@ export const TabsSidebarContent = ({
   upperContent,
   fetchUrl,
   databaseName,
-  queryDocs
+  queryDocs,
+  selectedNavItem
 }) => {
   return (
     <div className="with-sidebar tabs-with-sidebar content-area">
       <aside id="sidebar-content" className="scrollable">
-        <SidebarControllerContainer />
+        <SidebarControllerContainer selectedNavItem={selectedNavItem}/>
       </aside>
       <section id="dashboard-content" className="flex-layout flex-col">
         <div id="dashboard-upper-content">
@@ -126,6 +127,7 @@ TabsSidebarContent.propTypes = {
   hideFooter: PropTypes.bool,
   lowerContent: PropTypes.object,
   upperContent: PropTypes.object,
+  selectedNavItem: PropTypes.object
 };
 
 export const DocsTabsSidebarLayout = ({
@@ -173,12 +175,13 @@ export const DocsTabsSidebarLayout = ({
         fetchUrl={fetchUrl}
         databaseName={dbName}
         queryDocs={queryDocs}
+        selectedNavItem={selectedNavItem}
       />
     </div>
   );
 };
 
-export const ChangesSidebarLayout = ({ docURL, database, endpoint, dbName, 
dropDownLinks }) => {
+export const ChangesSidebarLayout = ({ docURL, database, endpoint, dbName, 
dropDownLinks, selectedNavItem }) => {
   return (
     <div id="dashboard" className="with-sidebar">
       <TabsSidebarHeader
@@ -193,12 +196,15 @@ export const ChangesSidebarLayout = ({ docURL, database, 
endpoint, dbName, dropD
         upperContent={<Changes.ChangesTabContent />}
         lowerContent={<Changes.ChangesController />}
         hideFooter={true}
+        selectedNavItem={selectedNavItem}
       />
     </div>
   );
 };
 
-export const ViewsTabsSidebarLayout = ({ showEditView, database, docURL, 
endpoint, dbName, dropDownLinks }) => {
+export const ViewsTabsSidebarLayout = ({showEditView, database, docURL, 
endpoint,
+  dbName, dropDownLinks, selectedNavItem }) => {
+
   const content = showEditView ? <IndexEditorComponents.EditorController /> : 
<DesignDocInfoComponents.DesignDocInfo />;
   return (
     <div id="dashboard" className="with-sidebar">
@@ -215,6 +221,7 @@ export const ViewsTabsSidebarLayout = ({ showEditView, 
database, docURL, endpoin
       <TabsSidebarContent
         lowerContent={content}
         hideFooter={true}
+        selectedNavItem={selectedNavItem}
       />
     </div>
   );
diff --git a/app/addons/documents/routes-documents.js 
b/app/addons/documents/routes-documents.js
index ceb33a337..453003a54 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -16,7 +16,7 @@ import BaseRoute from './shared-routes';
 import ChangesActions from './changes/actions';
 import Databases from '../databases/base';
 import Resources from './resources';
-import SidebarActions from './sidebar/actions';
+import {SidebarItemSelection} from './sidebar/helpers';
 import DesignDocInfoActions from './designdocinfo/actions';
 import ComponentsActions from '../components/actions';
 import {DocsTabsSidebarLayout, ViewsTabsSidebarLayout, ChangesSidebarLayout} 
from './layouts';
@@ -53,8 +53,7 @@ var DocumentsRouteObject = BaseRoute.extend({
       ddocName: ddoc,
       designDocInfo: designDocInfo
     });
-
-    SidebarActions.selectNavItem('designDoc', {
+    const selectedNavItem = new SidebarItemSelection('designDoc', {
       designDocName: ddoc,
       designDocSection: 'metadata'
     });
@@ -67,6 +66,7 @@ var DocumentsRouteObject = BaseRoute.extend({
       dbName={this.database.id}
       dropDownLinks={dropDownLinks}
       database={this.database}
+      selectedNavItem={selectedNavItem}
     />;
   },
 
@@ -90,10 +90,7 @@ var DocumentsRouteObject = BaseRoute.extend({
       tab = 'design-docs';
     }
 
-    const selectedNavItem = {
-      navItem: tab
-    };
-    SidebarActions.selectNavItem(selectedNavItem.navItem);
+    const selectedNavItem = new SidebarItemSelection(tab);
     ComponentsActions.showDeleteDatabaseModal({showDeleteModal: false, dbId: 
''});
 
     const endpoint = this.database.allDocs.urlRef("apiurl", {});
@@ -117,7 +114,7 @@ var DocumentsRouteObject = BaseRoute.extend({
     ChangesActions.initChanges({
       databaseName: this.database.id
     });
-    SidebarActions.selectNavItem('changes');
+    const selectedNavItem = new SidebarItemSelection('changes');
 
     return <ChangesSidebarLayout
       endpoint={FauxtonAPI.urls('changes', 'apiurl', this.database.id, '')}
@@ -125,6 +122,7 @@ var DocumentsRouteObject = BaseRoute.extend({
       dbName={this.database.id}
       dropDownLinks={this.getCrumbs(this.database)}
       database={this.database}
+      selectedNavItem={selectedNavItem}
     />;
   }
 
diff --git a/app/addons/documents/routes-index-editor.js 
b/app/addons/documents/routes-index-editor.js
index ad19c8c84..935ff4763 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -15,7 +15,8 @@ import FauxtonAPI from "../../core/api";
 import BaseRoute from "./shared-routes";
 import ActionsIndexEditor from "./index-editor/actions";
 import Databases from "../databases/base";
-import SidebarActions from "./sidebar/actions";
+import SidebarActions from './sidebar/actions';
+import {SidebarItemSelection} from './sidebar/helpers';
 import {DocsTabsSidebarLayout, ViewsTabsSidebarLayout} from './layouts';
 
 const IndexEditorAndResults = BaseRoute.extend({
@@ -59,15 +60,12 @@ const IndexEditorAndResults = BaseRoute.extend({
       designDocId: '_design/' + ddoc
     });
 
-    const selectedNavItem = {
-      navItem: 'designDoc',
-      params: {
-        designDocName: ddoc,
-        designDocSection: 'Views',
-        indexName: viewName
-      }
-    };
-    SidebarActions.selectNavItem(selectedNavItem.navItem, 
selectedNavItem.params);
+    const selectedNavItem = new SidebarItemSelection('designDoc', {
+      designDocName: ddoc,
+      designDocSection: 'Views',
+      indexName: viewName
+    });
+    SidebarActions.dispatchExpandSelectedItem(selectedNavItem);
 
     const url = FauxtonAPI.urls('view', 'server', 
encodeURIComponent(databaseName),
       encodeURIComponent(ddoc), encodeURIComponent(viewName));
@@ -107,8 +105,7 @@ const IndexEditorAndResults = BaseRoute.extend({
       newDesignDoc: newDesignDoc
     });
 
-    SidebarActions.selectNavItem('');
-
+    const selectedNavItem = new SidebarItemSelection('');
     const dropDownLinks = this.getCrumbs(this.database);
 
     return <ViewsTabsSidebarLayout
@@ -117,6 +114,7 @@ const IndexEditorAndResults = BaseRoute.extend({
       dbName={this.database.id}
       dropDownLinks={dropDownLinks}
       database={this.database}
+      selectedNavItem={selectedNavItem}
     />;
   },
 
@@ -129,11 +127,12 @@ const IndexEditorAndResults = BaseRoute.extend({
       designDocId: '_design/' + ddocName
     });
 
-    SidebarActions.selectNavItem('designDoc', {
+    const selectedNavItem = new SidebarItemSelection('designDoc', {
       designDocName: ddocName,
       designDocSection: 'Views',
       indexName: viewName
     });
+    SidebarActions.dispatchExpandSelectedItem(selectedNavItem);
 
     const docURL = FauxtonAPI.constants.DOC_URLS.GENERAL;
     const endpoint = FauxtonAPI.urls('view', 'apiurl', databaseName, ddocName, 
viewName);
@@ -146,6 +145,7 @@ const IndexEditorAndResults = BaseRoute.extend({
       dbName={this.database.id}
       dropDownLinks={dropDownLinks}
       database={this.database}
+      selectedNavItem={selectedNavItem}
     />;
   }
 
diff --git a/app/addons/documents/routes-mango.js 
b/app/addons/documents/routes-mango.js
index 26120bfa0..c59b6f53c 100644
--- a/app/addons/documents/routes-mango.js
+++ b/app/addons/documents/routes-mango.js
@@ -15,7 +15,6 @@ import app from "../../app";
 import FauxtonAPI from "../../core/api";
 import Databases from "../databases/resources";
 import Documents from "./shared-resources";
-import SidebarActions from "./sidebar/actions";
 import {MangoLayoutContainer} from './mangolayout';
 
 const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
@@ -40,8 +39,6 @@ const MangoIndexEditorAndQueryEditor = 
FauxtonAPI.RouteObject.extend({
   },
 
   findUsingIndex: function (database) {
-    SidebarActions.selectNavItem('mango-query');
-
     const url = FauxtonAPI.urls(
       'allDocs', 'app', encodeURIComponent(this.databaseName), '?limit=' + 
FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT
     );
diff --git a/app/addons/documents/shared-routes.js 
b/app/addons/documents/shared-routes.js
index ef0d2bdcd..00169553e 100644
--- a/app/addons/documents/shared-routes.js
+++ b/app/addons/documents/shared-routes.js
@@ -44,7 +44,7 @@ var BaseRoute = FauxtonAPI.RouteObject.extend({
       options.selectedNavItem = selectedNavItem;
     }
 
-    SidebarActions.newOptions(options);
+    SidebarActions.dispatchNewOptions(options);
   },
 
   getCrumbs: function (database) {
diff --git a/app/addons/documents/sidebar/SidebarControllerContainer.js 
b/app/addons/documents/sidebar/SidebarControllerContainer.js
index 4da062887..3a3ea2621 100644
--- a/app/addons/documents/sidebar/SidebarControllerContainer.js
+++ b/app/addons/documents/sidebar/SidebarControllerContainer.js
@@ -12,25 +12,97 @@
 
 import { connect } from 'react-redux';
 import SidebarComponents from './sidebar';
-import ActionTypes from './actiontypes';
+import Action from './actions';
+import { getDatabase } from './reducers';
 
-const reduxUpdatedDesignDocList = (designDocs) => {
-  return {
-    type: ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS,
-    options: {
-      designDocs: Array.isArray(designDocs) ? designDocs : []
-    }
+
+// returns a simple array of design doc IDs
+const getAvailableDesignDocs = (state) => {
+  const availableDocs = state.designDocs.filter((doc) => {
+    return !doc.isMangoDoc();
+  });
+  return _.map(availableDocs, (doc) => {
+    return doc.id;
+  });
+};
+
+const getDeleteIndexDesignDoc = (state) => {
+  const designDoc = state.designDocs.find((ddoc) => {
+    return '_design/' + state.deleteIndexModalDesignDocName === ddoc.id;
+  });
+
+  return designDoc ? designDoc.dDocModel() : null;
+};
+
+
+const selectedNavItem = (selectedItem) => {
+
+  // resets previous selection and sets new values
+  const settings = {
+    designDocName: '',
+    designDocSection: '',
+    indexName: '',
+    navItem: '',
+    ...selectedItem
   };
+  return settings;
 };
 
-const mapStateToProps = () => {
-  return {};
+const mapStateToProps = ({ sidebar }, ownProps) => {
+  return {
+    database: getDatabase(sidebar),
+    selectedNav: selectedNavItem(ownProps.selectedNavItem),
+    designDocs: sidebar.designDocs,
+    // designDocList: getDesignDocList(sidebar),
+    designDocList: sidebar.designDocList,
+    availableDesignDocIds: getAvailableDesignDocs(sidebar),
+    toggledSections: sidebar.toggledSections,
+    isLoading: sidebar.loading,
+
+    deleteIndexModalVisible: sidebar.deleteIndexModalVisible,
+    deleteIndexModalText: sidebar.deleteIndexModalText,
+    deleteIndexModalOnSubmit: sidebar.deleteIndexModalOnSubmit,
+    deleteIndexModalIndexName: sidebar.deleteIndexModalIndexName,
+    deleteIndexModalDesignDoc: getDeleteIndexDesignDoc(sidebar),
+
+    cloneIndexModalVisible: sidebar.cloneIndexModalVisible,
+    cloneIndexModalTitle: sidebar.cloneIndexModalTitle,
+    cloneIndexModalSelectedDesignDoc: sidebar.cloneIndexModalSelectedDesignDoc,
+    cloneIndexModalNewDesignDocName: sidebar.cloneIndexModalNewDesignDocName,
+    cloneIndexModalOnSubmit: sidebar.cloneIndexModalOnSubmit,
+    cloneIndexDesignDocProp: sidebar.cloneIndexDesignDocProp,
+    cloneIndexModalNewIndexName: sidebar.cloneIndexModalNewIndexName,
+    cloneIndexSourceIndexName: sidebar.cloneIndexModalSourceIndexName,
+    cloneIndexSourceDesignDocName: sidebar.cloneIndexModalSourceDesignDocName,
+    cloneIndexModalIndexLabel: sidebar.cloneIndexModalIndexLabel
+  };
 };
 
 const mapDispatchToProps = (dispatch) => {
   return {
-    reduxUpdatedDesignDocList: (designDocsList) => {
-      dispatch(reduxUpdatedDesignDocList(designDocsList));
+    toggleContent: (designDoc, indexGroup) => {
+      dispatch(Action.toggleContent(designDoc, indexGroup));
+    },
+    hideCloneIndexModal: () => {
+      dispatch(Action.hideCloneIndexModal());
+    },
+    hideDeleteIndexModal: () => {
+      dispatch(Action.hideDeleteIndexModal());
+    },
+    showDeleteIndexModal: (indexName, designDocName, indexLabel, onDelete) => {
+      dispatch(Action.showDeleteIndexModal(indexName, designDocName, 
indexLabel, onDelete));
+    },
+    showCloneIndexModal: (indexName, designDocName, indexLabel, onSubmit) => {
+      dispatch(Action.showCloneIndexModal(indexName, designDocName, 
indexLabel, onSubmit));
+    },
+    selectDesignDoc: (designDoc) => {
+      dispatch(Action.selectDesignDoc(designDoc));
+    },
+    updateNewDesignDocName: (designDocName) => {
+      dispatch(Action.updateNewDesignDocName(designDocName));
+    },
+    setNewCloneIndexName: (indexName) => {
+      dispatch(Action.setNewCloneIndexName(indexName));
     }
   };
 };
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.actions.test.js 
b/app/addons/documents/sidebar/__tests__/sidebar.actions.test.js
index e9393c160..a7fbb59e8 100644
--- a/app/addons/documents/sidebar/__tests__/sidebar.actions.test.js
+++ b/app/addons/documents/sidebar/__tests__/sidebar.actions.test.js
@@ -20,6 +20,15 @@ FauxtonAPI.router = new FauxtonAPI.Router([]);
 
 describe('Sidebar actions', () => {
 
+  beforeEach(() => {
+    FauxtonAPI.reduxState = sinon.stub().returns({
+      sidebar:{
+        loading: true
+      }
+    });
+    FauxtonAPI.reduxDispatch = sinon.stub();
+  });
+
   afterEach(() => {
     restore(FauxtonAPI.navigate);
     restore(FauxtonAPI.addNotification);
@@ -47,7 +56,7 @@ describe('Sidebar actions', () => {
       }
     };
 
-    Actions.newOptions(options);
+    Actions.dispatchNewOptions(options);
     process.nextTick(() => {
       assert.ok(notificationSpy.calledOnce);
       assert.ok(/not exist/.test(notificationSpy.args[0][0].msg));
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.components.test.js 
b/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
index 75733cbab..4e306b5a9 100644
--- a/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
+++ b/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
@@ -52,7 +52,9 @@ describe('DesignDoc', () => {
       designDocName={'doc-$-#-.1'}
       selectedNavInfo={selectedNavInfo}
       toggledSections={{}}
-      designDoc={{}} />);
+      designDoc={{}}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}} />);
 
     assert.include(wrapper.find('a.icon 
.fonticon-plus-circled').at(1).props()['href'], '/doc-%24-%23-.1');
     assert.include(wrapper.find('a.toggle-view 
.accordion-header').props()['href'], '/doc-%24-%23-.1');
@@ -70,7 +72,9 @@ describe('DesignDoc', () => {
       designDocName={'id#1'}
       selectedNavInfo={{}}
       toggledSections={{}}
-      designDoc={{}} />);
+      designDoc={{}}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}} />);
 
     // NOTE: wrapper.find doesn't work special chars so we use class name 
instead
     wrapper.find('div.accordion-list-item').simulate('click', {preventDefault: 
sinon.stub()});
@@ -89,6 +93,8 @@ describe('DesignDoc', () => {
       toggledSections={{}}
       designDoc={{ customProp: { one: 'something' } }}
       designDocName={'doc-$-#-.1'}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -114,6 +120,8 @@ describe('DesignDoc', () => {
       toggledSections={{}}
       designDoc={{ customProp: { one: 'something' } }}
       designDocName={'doc-$-#-.1'}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -139,6 +147,8 @@ describe('DesignDoc', () => {
       designDoc={{}} // note that this is empty
       designDocName={'doc-$-#-.1'}
       toggledSections={{}}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}}
     />);
 
     const subOptions = el.find('.accordion-body li');
@@ -159,7 +169,9 @@ describe('DesignDoc', () => {
       }}
       designDocName={'doc-$-#-.1'}
       toggledSections={{}}
-      designDoc={{}} />);
+      designDoc={{}}
+      showDeleteIndexModal={() => {}}
+      showCloneIndexModal={() => {}} />);
 
     assert.equal(el.find('.accordion-body li.active a').text(), 'Metadata');
   });
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.reducers.test.js 
b/app/addons/documents/sidebar/__tests__/sidebar.reducers.test.js
new file mode 100644
index 000000000..dc4ab1042
--- /dev/null
+++ b/app/addons/documents/sidebar/__tests__/sidebar.reducers.test.js
@@ -0,0 +1,77 @@
+// 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 sidebar from "../reducers";
+import ActionTypes from "../actiontypes";
+import testUtils from "../../../../../test/mocha/testUtils";
+
+const assert = testUtils.assert;
+
+function isVisible (state, designDoc, indexGroup) {
+  if (!state.toggledSections[designDoc]) {
+    return false;
+  }
+  if (indexGroup) {
+    return state.toggledSections[designDoc].indexGroups[indexGroup];
+  }
+  return state.toggledSections[designDoc].visible;
+}
+
+describe('Sidebar Reducer', () => {
+
+  describe('toggle state', () => {
+
+    it('should be visible after being toggled', () => {
+      const designDoc = 'designDoc';
+      const action = {
+        type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
+        designDoc: designDoc
+      };
+      const newState = sidebar(undefined, action);
+      assert.ok(isVisible(newState, designDoc));
+    });
+
+    it('should not be visible after being toggled twice', () => {
+      const designDoc = 'designDoc2';
+      const action = {
+        type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
+        designDoc: designDoc
+      };
+      let newState = sidebar(undefined, action);
+      newState = sidebar(newState, action);
+      assert.notOk(isVisible(newState, designDoc));
+    });
+
+  });
+
+  describe('toggle state for index', () => {
+    const designDoc = 'design-doc';
+    const indexGroup = 'index';
+    const action = {
+      type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
+      designDoc: designDoc,
+      indexGroup: indexGroup
+    };
+
+    it('should toggle the state', () => {
+      let newState = sidebar(undefined, action);
+      assert.ok(isVisible(newState, designDoc));
+
+      newState = sidebar(newState, action);
+      assert.ok(isVisible(newState, designDoc, indexGroup));
+
+      newState = sidebar(newState, action);
+      assert.notOk(isVisible(newState, designDoc, indexGroup));
+    });
+
+  });
+});
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.stores.test.js 
b/app/addons/documents/sidebar/__tests__/sidebar.stores.test.js
deleted file mode 100644
index 227b66c05..000000000
--- a/app/addons/documents/sidebar/__tests__/sidebar.stores.test.js
+++ /dev/null
@@ -1,74 +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 testUtils from "../../../../../test/mocha/testUtils";
-const assert = testUtils.assert;
-let dispatchToken;
-let store;
-
-describe('Sidebar Store', () => {
-  beforeEach(() => {
-    store = new Stores.SidebarStore();
-    dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch.bind(store));
-  });
-
-  afterEach(() => {
-    FauxtonAPI.dispatcher.unregister(dispatchToken);
-  });
-
-  describe('toggle state', () => {
-
-    it('should not be visible if never toggled', () => {
-      assert.notOk(store.isVisible('designDoc'));
-    });
-
-    it('should be visible after being toggled', () => {
-      var designDoc = 'designDoc';
-      store.toggleContent(designDoc);
-      assert.ok(store.isVisible(designDoc));
-    });
-
-    it('should not be visible after being toggled twice', () => {
-      var designDoc = 'designDoc';
-      store.toggleContent(designDoc);
-      store.toggleContent(designDoc);
-      assert.notOk(store.isVisible(designDoc));
-    });
-
-  });
-
-  describe('toggle state for index', () => {
-    var designDoc = 'design-doc';
-
-    beforeEach(() => {
-      store.toggleContent(designDoc);
-    });
-
-    it('should be hidden if never toggled', () => {
-      assert.notOk(store.isVisible(designDoc, 'index'));
-    });
-
-    it('should be if toggled', () => {
-      store.toggleContent(designDoc, 'index');
-      assert.ok(store.isVisible(designDoc, 'index'));
-    });
-
-    it('should be hidden after being toggled twice', () => {
-      store.toggleContent(designDoc, 'index');
-      store.toggleContent(designDoc, 'index');
-      assert.notOk(store.isVisible(designDoc, 'index'));
-    });
-
-  });
-});
diff --git a/app/addons/documents/sidebar/actions.js 
b/app/addons/documents/sidebar/actions.js
index ab93c2cb0..7564b3c36 100644
--- a/app/addons/documents/sidebar/actions.js
+++ b/app/addons/documents/sidebar/actions.js
@@ -12,18 +12,23 @@
 
 import FauxtonAPI from "../../../core/api";
 import ActionTypes from "./actiontypes";
-import Stores from "./stores";
-var store = Stores.sidebarStore;
 
-function newOptions (options) {
-  if (options.database.safeID() !== store.getDatabaseName()) {
-    FauxtonAPI.dispatch({
+const _getDatabaseName = ({sidebar}) => {
+  if (!sidebar || sidebar.loading) {
+    return '';
+  }
+  return sidebar.database.safeID();
+};
+
+const dispatchNewOptions = (options) => {
+  if (options.database.safeID() !== _getDatabaseName(FauxtonAPI.reduxState())) 
{
+    FauxtonAPI.reduxDispatch({
       type: ActionTypes.SIDEBAR_FETCHING
     });
   }
 
   options.designDocs.fetch().then(() => {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: ActionTypes.SIDEBAR_NEW_OPTIONS,
       options: options
     });
@@ -40,56 +45,48 @@ function newOptions (options) {
       clear:  true
     });
   });
-}
+};
 
-function updateDesignDocs (designDocs) {
-  FauxtonAPI.dispatch({
+const dispatchUpdateDesignDocs = (designDocs) => {
+  FauxtonAPI.reduxDispatch({
     type: ActionTypes.SIDEBAR_FETCHING
   });
 
   designDocs.fetch().then(function () {
-    FauxtonAPI.dispatch({
+    FauxtonAPI.reduxDispatch({
       type: ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS,
       options: {
         designDocs: designDocs
       }
     });
   });
-}
+};
+
+const dispatchHideDeleteIndexModal = () => {
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL
+  });
+};
+
+const dispatchExpandSelectedItem = (selectedNavItem) => {
+  FauxtonAPI.reduxDispatch({
+    type: ActionTypes.SIDEBAR_EXPAND_SELECTED_ITEM,
+    options: {
+      selectedNavItem: selectedNavItem
+    }
+  });
+};
 
-function toggleContent (designDoc, indexGroup) {
-  FauxtonAPI.dispatch({
+const toggleContent = (designDoc, indexGroup) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_TOGGLE_CONTENT,
     designDoc: designDoc,
     indexGroup: indexGroup
   });
-}
-
-// This selects any item in the sidebar, including nested nav items to ensure 
the appropriate item is visible
-// and highlighted. Params:
-// - `navItem`: 'permissions', 'changes', 'all-docs', 'compact', 
'mango-query', 'designDoc' (or anything thats been
-//    extended)
-// - `params`: optional object if you passed designDoc as the first param. 
This lets you specify which sub-page
-//    should be selected, e.g.
-//       Actions.selectNavItem('designDoc', { designDocName: 'my-design-doc', 
section: 'metadata' });
-//       Actions.selectNavItem('designDoc', { designDocName: 'my-design-doc', 
section: 'Views', indexName: 'my-view' });
-function selectNavItem (navItem, params) {
-  const settings = {
-    designDocName: '',
-    designDocSection: '',
-    indexName: '',
-    ...params
-  };
-  settings.navItem = navItem;
-
-  FauxtonAPI.dispatch({
-    type: ActionTypes.SIDEBAR_SET_SELECTED_NAV_ITEM,
-    options: settings
-  });
-}
+};
 
-function showDeleteIndexModal (indexName, designDocName, indexLabel, onDelete) 
{
-  FauxtonAPI.dispatch({
+const showDeleteIndexModal = (indexName, designDocName, indexLabel, onDelete) 
=> (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_SHOW_DELETE_INDEX_MODAL,
     options: {
       indexName: indexName,
@@ -98,14 +95,16 @@ function showDeleteIndexModal (indexName, designDocName, 
indexLabel, onDelete) {
       onDelete: onDelete
     }
   });
-}
+};
 
-function hideDeleteIndexModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL });
-}
+const hideDeleteIndexModal = () => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL
+  });
+};
 
-function showCloneIndexModal (indexName, designDocName, indexLabel, onSubmit) {
-  FauxtonAPI.dispatch({
+const showCloneIndexModal = (indexName, designDocName, indexLabel, onSubmit) 
=> (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_SHOW_CLONE_INDEX_MODAL,
     options: {
       sourceIndexName: indexName,
@@ -115,50 +114,52 @@ function showCloneIndexModal (indexName, designDocName, 
indexLabel, onSubmit) {
       cloneIndexModalTitle: 'Clone ' + indexLabel
     }
   });
-}
+};
 
-function hideCloneIndexModal () {
-  FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL });
-}
+const hideCloneIndexModal = () => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL
+  });
+};
 
-function updateNewDesignDocName (designDocName) {
-  FauxtonAPI.dispatch({
+const updateNewDesignDocName = (designDocName) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED,
     options: {
       value: designDocName
     }
   });
-}
+};
 
-function selectDesignDoc (designDoc) {
-  FauxtonAPI.dispatch({
+const selectDesignDoc = (designDoc) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE,
     options: {
       value: designDoc
     }
   });
-}
+};
 
-function setNewCloneIndexName (indexName) {
-  FauxtonAPI.dispatch({
+const setNewCloneIndexName = (indexName) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME,
     options: {
       value: indexName
     }
   });
-}
-
+};
 
 export default {
-  newOptions: newOptions,
-  updateDesignDocs: updateDesignDocs,
-  toggleContent: toggleContent,
-  selectNavItem: selectNavItem,
-  showDeleteIndexModal: showDeleteIndexModal,
-  hideDeleteIndexModal: hideDeleteIndexModal,
-  showCloneIndexModal: showCloneIndexModal,
-  hideCloneIndexModal: hideCloneIndexModal,
-  updateNewDesignDocName: updateNewDesignDocName,
-  selectDesignDoc: selectDesignDoc,
-  setNewCloneIndexName: setNewCloneIndexName
+  dispatchNewOptions,
+  dispatchUpdateDesignDocs,
+  toggleContent,
+  showDeleteIndexModal,
+  hideDeleteIndexModal,
+  dispatchHideDeleteIndexModal,
+  showCloneIndexModal,
+  hideCloneIndexModal,
+  updateNewDesignDocName,
+  selectDesignDoc,
+  setNewCloneIndexName,
+  dispatchExpandSelectedItem
 };
diff --git a/app/addons/documents/sidebar/actiontypes.js 
b/app/addons/documents/sidebar/actiontypes.js
index f08d4bc0d..666b8a97b 100644
--- a/app/addons/documents/sidebar/actiontypes.js
+++ b/app/addons/documents/sidebar/actiontypes.js
@@ -11,7 +11,7 @@
 // the License.
 
 export default {
-  SIDEBAR_SET_SELECTED_NAV_ITEM: 'SIDEBAR_SET_SELECTED_NAV_ITEM',
+  SIDEBAR_EXPAND_SELECTED_ITEM: 'SIDEBAR_EXPAND_SELECTED_ITEM',
   SIDEBAR_NEW_OPTIONS: 'SIDEBAR_NEW_OPTIONS',
   SIDEBAR_TOGGLE_CONTENT: 'SIDEBAR_TOGGLE_CONTENT',
   SIDEBAR_FETCHING: 'SIDEBAR_FETCHING',
diff --git a/app/addons/documents/sidebar/components/CloneIndexModal.js 
b/app/addons/documents/sidebar/components/CloneIndexModal.js
new file mode 100644
index 000000000..220e8abc3
--- /dev/null
+++ b/app/addons/documents/sidebar/components/CloneIndexModal.js
@@ -0,0 +1,114 @@
+// 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 { Modal } from 'react-bootstrap';
+import ReactDOM from 'react-dom';
+import FauxtonAPI from '../../../../core/api';
+import IndexEditorComponents from '../../index-editor/components';
+
+const { DesignDocSelector } = IndexEditorComponents;
+
+export default class CloneIndexModal extends React.Component {
+  static propTypes = {
+    visible: PropTypes.bool.isRequired,
+    title: PropTypes.string,
+    close: PropTypes.func.isRequired,
+    submit: PropTypes.func.isRequired,
+    designDocArray: PropTypes.array.isRequired,
+    selectedDesignDoc: PropTypes.string.isRequired,
+    newDesignDocName: PropTypes.string.isRequired,
+    newIndexName: PropTypes.string.isRequired,
+    indexLabel: PropTypes.string.isRequired,
+    selectDesignDoc: PropTypes.func.isRequired,
+    updateNewDesignDocName: PropTypes.func.isRequired,
+    setNewCloneIndexName: PropTypes.func.isRequired
+  };
+
+  static defaultProps = {
+    title: 'Clone Index',
+    visible: false
+  };
+
+  constructor(props) {
+    super(props);
+    this.props.setNewCloneIndexName('');
+  }
+
+  submit = () => {
+    if (!this.designDocSelector.validate()) {
+      return;
+    }
+    if (this.props.newIndexName === '') {
+      FauxtonAPI.addNotification({
+        msg: 'Please enter the new index name.',
+        type: 'error',
+        clear: true
+      });
+      return;
+    }
+    this.props.submit();
+  };
+
+  close = (e) => {
+    if (e) {
+      e.preventDefault();
+    }
+    this.props.close();
+  };
+
+  setNewIndexName = (e) => {
+    this.props.setNewCloneIndexName(e.target.value);
+  };
+
+  render() {
+    return (
+      <Modal dialogClassName="clone-index-modal" show={this.props.visible} 
onHide={this.close}>
+        <Modal.Header closeButton={true}>
+          <Modal.Title>{this.props.title}</Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+
+          <form className="form" method="post" onSubmit={this.submit}>
+            <p>
+              Select the design document where the cloned 
{this.props.indexLabel} will be created, and then enter
+              a name for the cloned {this.props.indexLabel}.
+            </p>
+
+            <div className="row">
+              <DesignDocSelector
+                ref={node => this.designDocSelector = node}
+                designDocList={this.props.designDocArray}
+                selectedDesignDocName={this.props.selectedDesignDoc}
+                newDesignDocName={this.props.newDesignDocName}
+                onSelectDesignDoc={this.props.selectDesignDoc}
+                onChangeNewDesignDocName={this.props.updateNewDesignDocName} />
+            </div>
+
+            <div className="clone-index-name-row">
+              <label className="new-index-title-label" 
htmlFor="new-index-name">{this.props.indexLabel} Name</label>
+              <input type="text" id="new-index-name" 
value={this.props.newIndexName} onChange={this.setNewIndexName}
+                placeholder="New view name" />
+            </div>
+          </form>
+
+        </Modal.Body>
+        <Modal.Footer>
+          <a href="#" className="cancel-link" onClick={this.close} 
data-bypass="true">Cancel</a>
+          <button onClick={this.submit} data-bypass="true" className="btn 
btn-primary save">
+            <i className="icon fonticon-ok-circled" /> Clone 
{this.props.indexLabel}</button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/DesignDoc.js 
b/app/addons/documents/sidebar/components/DesignDoc.js
new file mode 100644
index 000000000..c0cc2e54b
--- /dev/null
+++ b/app/addons/documents/sidebar/components/DesignDoc.js
@@ -0,0 +1,158 @@
+// 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 { Collapse } from 'react-bootstrap';
+import ReactDOM from 'react-dom';
+import FauxtonAPI from '../../../../core/api';
+import Components from '../../../components/react-components';
+import IndexEditorActions from '../../index-editor/actions';
+import IndexSection from './IndexSection';
+
+const { MenuDropDown } = Components;
+
+export default class DesignDoc extends React.Component {
+  static propTypes = {
+    database: PropTypes.object.isRequired,
+    sidebarListTypes: PropTypes.array.isRequired,
+    isExpanded: PropTypes.bool.isRequired,
+    selectedNavInfo: PropTypes.object.isRequired,
+    toggledSections: PropTypes.object.isRequired,
+    designDocName:  PropTypes.string.isRequired,
+    showDeleteIndexModal: PropTypes.func.isRequired,
+    showCloneIndexModal: PropTypes.func.isRequired
+  };
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      updatedSidebarListTypes: this.props.sidebarListTypes
+    };
+    if (_.isEmpty(this.state.updatedSidebarListTypes) ||
+      (_.has(this.state.updatedSidebarListTypes[0], 'selector') && 
this.state.updatedSidebarListTypes[0].selector !== 'views')) {
+
+      const newList = this.state.updatedSidebarListTypes;
+      newList.unshift({
+        selector: 'views',
+        name: 'Views',
+        urlNamespace: 'view',
+        indexLabel: 'view',
+        onDelete: IndexEditorActions.deleteView,
+        onClone: IndexEditorActions.cloneView,
+        onEdit: IndexEditorActions.gotoEditViewPage
+      });
+      this.state = { updatedSidebarListTypes: newList };
+    }
+  }
+
+  indexList = () => {
+    return _.map(this.state.updatedSidebarListTypes, (index, key) => {
+      const expanded = _.has(this.props.toggledSections, index.name) && 
this.props.toggledSections[index.name];
+
+      // if an index in this list is selected, pass that down
+      let selectedIndex = '';
+      if (this.props.selectedNavInfo.designDocSection === index.name) {
+        selectedIndex = this.props.selectedNavInfo.indexName;
+      }
+
+      return (
+        <IndexSection
+          icon={index.icon}
+          isExpanded={expanded}
+          urlNamespace={index.urlNamespace}
+          indexLabel={index.indexLabel}
+          onEdit={index.onEdit}
+          onDelete={index.onDelete}
+          onClone={index.onClone}
+          selectedIndex={selectedIndex}
+          toggle={this.props.toggle}
+          database={this.props.database}
+          designDocName={this.props.designDocName}
+          key={key}
+          title={index.name}
+          selector={index.selector}
+          items={_.keys(this.props.designDoc[index.selector])}
+          showDeleteIndexModal={this.props.showDeleteIndexModal}
+          showCloneIndexModal={this.props.showCloneIndexModal} />
+      );
+    });
+  };
+
+  toggle = (e) => {
+    e.preventDefault();
+    this.props.toggle(this.props.designDocName);
+  };
+
+  getNewButtonLinks = () => {
+    const newUrlPrefix = FauxtonAPI.urls('databaseBaseURL', 'app', 
encodeURIComponent(this.props.database.id));
+    const designDocName = this.props.designDocName;
+
+    const addNewLinks = _.reduce(FauxtonAPI.getExtensions('sidebar:links'), 
function (menuLinks, link) {
+      menuLinks.push({
+        title: link.title,
+        url: '#' + newUrlPrefix + '/' + link.url + '/' + 
encodeURIComponent(designDocName),
+        icon: 'fonticon-plus-circled'
+      });
+      return menuLinks;
+    }, [{
+      title: 'New View',
+      url: '#' + FauxtonAPI.urls('new', 'addView', 
encodeURIComponent(this.props.database.id), encodeURIComponent(designDocName)),
+      icon: 'fonticon-plus-circled'
+    }]);
+
+    return [{
+      title: 'Add New',
+      links: addNewLinks
+    }];
+  };
+
+  render () {
+    const buttonLinks = this.getNewButtonLinks();
+    let toggleClassNames = 'design-doc-section accordion-header';
+    let toggleBodyClassNames = 'design-doc-body accordion-body collapse';
+
+    if (this.props.isExpanded) {
+      toggleClassNames += ' down';
+      toggleBodyClassNames += ' in';
+    }
+    const designDocName = this.props.designDocName;
+    const designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', 
encodeURIComponent(this.props.database.id), designDocName);
+    const metadataRowClass = (this.props.selectedNavInfo.designDocSection === 
'metadata') ? 'active' : '';
+
+    return (
+      <li className="nav-header">
+        <div id={"sidebar-tab-" + designDocName} className={toggleClassNames}>
+          <div id={"nav-header-" + designDocName} onClick={this.toggle} 
className='accordion-list-item'>
+            <div className="fonticon-play"></div>
+            <p className='design-doc-name'>
+              <span title={'_design/' + designDocName}>{designDocName}</span>
+            </p>
+          </div>
+          <div className='new-button add-dropdown'>
+            <MenuDropDown links={buttonLinks} />
+          </div>
+        </div>
+        <Collapse in={this.props.isExpanded}>
+          <ul className={toggleBodyClassNames} id={this.props.designDocName}>
+            <li className={metadataRowClass}>
+              <a href={"#/" + designDocMetaUrl} className="toggle-view 
accordion-header">
+                Metadata
+              </a>
+            </li>
+            {this.indexList()}
+          </ul>
+        </Collapse>
+      </li>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/DesignDocList.js 
b/app/addons/documents/sidebar/components/DesignDocList.js
new file mode 100644
index 000000000..79deacedc
--- /dev/null
+++ b/app/addons/documents/sidebar/components/DesignDocList.js
@@ -0,0 +1,82 @@
+// 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 DesignDoc from './DesignDoc';
+
+export default class DesignDocList extends React.Component {
+  static propTypes = {
+    database: PropTypes.object.isRequired,
+    toggle: PropTypes.func.isRequired,
+    designDocs: PropTypes.array,
+    toggledSections: PropTypes.object,
+    selectedNav: PropTypes.shape({
+      designDocName: PropTypes.string,
+      designDocSection: PropTypes.string,
+      indexName: PropTypes.string,
+      navItem: PropTypes.string
+    }).isRequired,
+    showDeleteIndexModal: PropTypes.func.isRequired,
+    showCloneIndexModal: PropTypes.func.isRequired
+  };
+
+  constructor(props) {
+    super(props);
+    const list = FauxtonAPI.getExtensions('sidebar:list');
+    this.sidebarListTypes = _.isUndefined(list) ? [] : list;
+  }
+
+  designDocList = () => {
+    return _.map(this.props.designDocs, (designDoc, key) => {
+      const ddName = decodeURIComponent(designDoc.safeId);
+
+      // only pass down the selected nav info and toggle info if they're 
relevant for this particular design doc
+      let expanded = false,
+          toggledSections = {};
+      if (_.has(this.props.toggledSections, ddName)) {
+        expanded = this.props.toggledSections[ddName].visible;
+        toggledSections = this.props.toggledSections[ddName].indexGroups;
+      }
+
+      let selectedNavInfo = {};
+      if (this.props.selectedNav.navItem === 'designDoc' && 
this.props.selectedNav.designDocName === ddName) {
+        selectedNavInfo = this.props.selectedNav;
+      }
+
+      return (
+        <DesignDoc
+          toggle={this.props.toggle}
+          sidebarListTypes={this.sidebarListTypes}
+          isExpanded={expanded}
+          toggledSections={toggledSections}
+          selectedNavInfo={selectedNavInfo}
+          key={key}
+          designDoc={designDoc}
+          designDocName={ddName}
+          database={this.props.database}
+          showDeleteIndexModal={this.props.showDeleteIndexModal}
+          showCloneIndexModal={this.props.showCloneIndexModal} />
+      );
+    });
+  };
+
+  render() {
+    return (
+      <ul className="nav nav-list">
+        {this.designDocList()}
+      </ul>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/IndexSection.js 
b/app/addons/documents/sidebar/components/IndexSection.js
new file mode 100644
index 000000000..cbee3a8dc
--- /dev/null
+++ b/app/addons/documents/sidebar/components/IndexSection.js
@@ -0,0 +1,151 @@
+// 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 { Collapse, OverlayTrigger, Popover } from 'react-bootstrap';
+import ReactDOM from 'react-dom';
+import FauxtonAPI from '../../../../core/api';
+
+export default class IndexSection extends React.Component {
+  static propTypes = {
+    urlNamespace: PropTypes.string.isRequired,
+    indexLabel: PropTypes.string.isRequired,
+    database: PropTypes.object.isRequired,
+    designDocName: PropTypes.string.isRequired,
+    items: PropTypes.array.isRequired,
+    isExpanded: PropTypes.bool.isRequired,
+    selectedIndex: PropTypes.string.isRequired,
+    onDelete: PropTypes.func.isRequired,
+    onClone: PropTypes.func.isRequired,
+    showDeleteIndexModal: PropTypes.func.isRequired,
+    showCloneIndexModal: PropTypes.func.isRequired
+  };
+
+  state = {
+    placement: 'bottom'
+  };
+
+  // this dynamically changes the placement of the menu (top/bottom) to 
prevent it going offscreen and causing some
+  // unsightly shifting
+  setPlacement = (rowId) => {
+    const rowTop = document.getElementById(rowId).getBoundingClientRect().top;
+    const toggleHeight = 150; // the height of the menu overlay, arrow, view 
row
+    const placement = (rowTop + toggleHeight > window.innerHeight) ? 'top' : 
'bottom';
+    this.setState({ placement: placement });
+  };
+
+  createItems = () => {
+
+    // sort the indexes alphabetically
+    const sortedItems = this.props.items.sort();
+
+    return _.map(sortedItems, (indexName, index) => {
+      const href = FauxtonAPI.urls(this.props.urlNamespace, 'app', 
encodeURIComponent(this.props.database.id), 
encodeURIComponent(this.props.designDocName));
+      const className = (this.props.selectedIndex === indexName) ? 'active' : 
'';
+
+      return (
+        <li className={className} key={index}>
+          <a
+            id={this.props.designDocName + '_' + indexName}
+            href={"#/" + href + encodeURIComponent(indexName)}
+            className="toggle-view">
+            {indexName}
+          </a>
+          <OverlayTrigger
+            trigger="click"
+            onEnter={this.setPlacement.bind(this, this.props.designDocName + 
'_' + indexName)}
+            placement={this.state.placement}
+            rootClose={true}
+            ref={overlay => this.itemOverlay = overlay}
+            overlay={
+              <Popover id="index-menu-component-popover">
+                <ul>
+                  <li onClick={this.indexAction.bind(this, 'edit', { 
indexName: indexName, onEdit: this.props.onEdit })}>
+                    <span className="fonticon fonticon-file-code-o"></span>
+                    Edit
+                  </li>
+                  <li onClick={this.indexAction.bind(this, 'clone', { 
indexName: indexName, onClone: this.props.onClone })}>
+                    <span className="fonticon fonticon-files-o"></span>
+                    Clone
+                  </li>
+                  <li onClick={this.indexAction.bind(this, 'delete', { 
indexName: indexName, onDelete: this.props.onDelete })}>
+                    <span className="fonticon fonticon-trash"></span>
+                    Delete
+                  </li>
+                </ul>
+              </Popover>
+            }>
+            <span className="index-menu-toggle fonticon 
fonticon-wrench2"></span>
+          </OverlayTrigger>
+        </li>
+      );
+    });
+  };
+
+  indexAction = (action, params, e) => {
+    e.preventDefault();
+
+    this.itemOverlay.hide();
+
+    switch (action) {
+      case 'delete':
+        this.props.showDeleteIndexModal(params.indexName, 
this.props.designDocName, this.props.indexLabel, params.onDelete);
+        break;
+      case 'clone':
+        this.props.showCloneIndexModal(params.indexName, 
this.props.designDocName, this.props.indexLabel, params.onClone);
+        break;
+      case 'edit':
+        params.onEdit(this.props.database.id, this.props.designDocName, 
params.indexName);
+        break;
+    }
+  };
+
+  toggle = (e) => {
+    e.preventDefault();
+    this.props.toggle(this.props.designDocName, this.props.title);
+  };
+
+  render() {
+
+    // if this section has no content, omit it to prevent clutter. Otherwise 
it would show a toggle option that
+    // would hide/show nothing
+    if (this.props.items.length === 0) {
+      return null;
+    }
+
+    let toggleClassNames = 'accordion-header index-group-header';
+    let toggleBodyClassNames = 'index-list accordion-body collapse';
+    if (this.props.isExpanded) {
+      toggleClassNames += ' down';
+      toggleBodyClassNames += ' in';
+    }
+
+    const title = this.props.title;
+    const designDocName = this.props.designDocName;
+    const linkId = "nav-design-function-" + designDocName + 
this.props.selector;
+
+    return (
+      <li id={linkId}>
+        <a className={toggleClassNames} data-toggle="collapse" 
onClick={this.toggle}>
+          <div className="fonticon-play"></div>
+          {title}
+        </a>
+        <Collapse in={this.props.isExpanded}>
+          <ul className={toggleBodyClassNames}>
+            {this.createItems()}
+          </ul>
+        </Collapse>
+      </li>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/MainSidebar.js 
b/app/addons/documents/sidebar/components/MainSidebar.js
new file mode 100644
index 000000000..14c40c0c2
--- /dev/null
+++ b/app/addons/documents/sidebar/components/MainSidebar.js
@@ -0,0 +1,98 @@
+// 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 app from "../../../../app";
+import FauxtonAPI from '../../../../core/api';
+import DocumentHelper from "../../../documents/helpers";
+import Components from '../../../components/react-components';
+
+const { MenuDropDown } = Components;
+
+export default class MainSidebar extends React.Component {
+  static propTypes = {
+    selectedNavItem: PropTypes.string.isRequired
+  };
+
+  getNewButtonLinks = () => {  // these are links for the sidebar '+' on All 
Docs and All Design Docs
+    return DocumentHelper.getNewButtonLinks(this.props.databaseName);
+  };
+
+  buildDocLinks = () => {
+    const base = FauxtonAPI.urls('base', 'app', this.props.databaseName);
+    return FauxtonAPI.getExtensions('docLinks').map((link) => {
+      return (
+        <li key={link.url} className={this.getNavItemClass(link.url)}>
+          <a id={link.url} href={base + link.url}>{link.title}</a>
+        </li>
+      );
+    });
+  };
+
+  getNavItemClass = (navItem) => {
+    return (navItem === this.props.selectedNavItem) ? 'active' : '';
+  };
+
+  render() {
+    const docLinks = this.buildDocLinks();
+    const dbEncoded = FauxtonAPI.url.encode(this.props.databaseName);
+    const changesUrl = '#' + FauxtonAPI.urls('changes', 'app', dbEncoded, '');
+    const permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', 
dbEncoded);
+    const databaseUrl = FauxtonAPI.urls('allDocs', 'app', dbEncoded, '');
+    const mangoQueryUrl = FauxtonAPI.urls('mango', 'query-app', dbEncoded);
+    const runQueryWithMangoText = app.i18n.en_US['run-query-with-mango'];
+    const buttonLinks = this.getNewButtonLinks();
+
+    return (
+      <ul className="nav nav-list">
+        <li className={this.getNavItemClass('all-docs')}>
+          <a id="all-docs"
+            href={"#/" + databaseUrl}
+            className="toggle-view">
+            All Documents
+          </a>
+          <div id="new-all-docs-button" className="add-dropdown">
+            <MenuDropDown links={buttonLinks} />
+          </div>
+        </li>
+        <li className={this.getNavItemClass('mango-query')}>
+          <a
+            id="mango-query"
+            href={'#' + mangoQueryUrl}
+            className="toggle-view">
+            {runQueryWithMangoText}
+          </a>
+        </li>
+        <li className={this.getNavItemClass('permissions')}>
+          <a id="permissions" href={permissionsUrl}>Permissions</a>
+        </li>
+        <li className={this.getNavItemClass('changes')}>
+          <a id="changes" href={changesUrl}>Changes</a>
+        </li>
+        {docLinks}
+        <li className={this.getNavItemClass('design-docs')}>
+          <a
+            id="design-docs"
+            href={"#/" + databaseUrl + '?startkey="_design"&endkey="_design0"'}
+            className="toggle-view">
+            Design Documents
+          </a>
+          <div id="new-design-docs-button" className="add-dropdown">
+            <MenuDropDown links={buttonLinks} />
+          </div>
+        </li>
+      </ul>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/components/SidebarController.js 
b/app/addons/documents/sidebar/components/SidebarController.js
new file mode 100644
index 000000000..d9a548434
--- /dev/null
+++ b/app/addons/documents/sidebar/components/SidebarController.js
@@ -0,0 +1,146 @@
+// 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 ComponentsActions from "../../../components/actions";
+import Components from '../../../components/react-components';
+import ComponentsStore from '../../../components/stores';
+import GeneralComponents from '../../../fauxton/components';
+import CloneIndexModal from './CloneIndexModal';
+import DesignDocList from './DesignDocList';
+import MainSidebar from './MainSidebar';
+
+const { DeleteDatabaseModal, LoadLines } = Components;
+const { ConfirmationModal } = GeneralComponents;
+const { deleteDbModalStore } = ComponentsStore;
+
+export default class SidebarController extends React.Component {
+
+  static propTypes = {
+    selectedNav: PropTypes.shape({
+      designDocName: PropTypes.string,
+      designDocSection: PropTypes.string,
+      indexName: PropTypes.string,
+      navItem: PropTypes.string
+    }).isRequired
+  };
+
+  constructor(props) {
+    super(props);
+    this.state = this.getDeleteDbStoreState();
+    this.deleteIndex = this.deleteIndex.bind(this);
+    this.cloneIndex = this.cloneIndex.bind(this);
+  }
+
+  componentDidMount() {
+    deleteDbModalStore.on('change', this.onChange, this);
+  }
+
+  componentWillUnmount() {
+    deleteDbModalStore.off('change', this.onChange, this);
+  }
+
+  getDeleteDbStoreState() {
+    return {
+      deleteDbModalProperties: deleteDbModalStore.getShowDeleteDatabaseModal()
+    };
+  }
+
+  onChange = () => {
+    const newState = this.getDeleteDbStoreState();
+    this.setState(newState);
+  };
+
+  showDeleteDatabaseModal = (payload) => {
+    ComponentsActions.showDeleteDatabaseModal(payload);
+  };
+
+  // handles deleting of any index regardless of type. The delete handler and 
all relevant info is set when the user
+  // clicks the delete action for a particular index
+  deleteIndex = () => {
+
+    // if the user is currently on the index that's being deleted, pass that 
info along to the delete handler. That can
+    // be used to redirect the user to somewhere appropriate
+    const isOnIndex = this.props.selectedNav.navItem === 'designDoc' &&
+      ('_design/' + this.props.selectedNav.designDocName) === 
this.props.deleteIndexModalDesignDoc.id &&
+      this.props.selectedNav.indexName === 
this.props.deleteIndexModalIndexName;
+
+    this.props.deleteIndexModalOnSubmit({
+      isOnIndex: isOnIndex,
+      indexName: this.props.deleteIndexModalIndexName,
+      designDoc: this.props.deleteIndexModalDesignDoc,
+      designDocs: this.props.designDocs,
+      database: this.props.database
+    });
+  };
+
+  cloneIndex = () => {
+    this.props.cloneIndexModalOnSubmit({
+      sourceIndexName: this.props.cloneIndexSourceIndexName,
+      sourceDesignDocName: this.props.cloneIndexSourceDesignDocName,
+      targetDesignDocName: this.props.cloneIndexModalSelectedDesignDoc,
+      newDesignDocName: this.props.cloneIndexModalNewDesignDocName,
+      newIndexName: this.props.cloneIndexModalNewIndexName,
+      designDocs: this.props.designDocs,
+      database: this.props.database,
+      onComplete: this.props.hideCloneIndexModal
+    });
+  };
+
+  render() {
+    if (this.props.isLoading) {
+      return <LoadLines />;
+    }
+
+    return (
+      <nav className="sidenav">
+        <MainSidebar
+          selectedNavItem={this.props.selectedNav.navItem}
+          databaseName={this.props.database.id} />
+        <DesignDocList
+          selectedNav={this.props.selectedNav}
+          toggle={this.props.toggleContent}
+          toggledSections={this.props.toggledSections}
+          designDocs={this.props.designDocList}
+          database={this.props.database}
+          showDeleteIndexModal={this.props.showDeleteIndexModal}
+          showCloneIndexModal={this.props.showCloneIndexModal} />
+        <DeleteDatabaseModal
+          showHide={this.showDeleteDatabaseModal}
+          modalProps={this.state.deleteDbModalProperties} />
+
+        {/* the delete and clone index modals handle all index types, hence 
the props all being pulled from the store */}
+        <ConfirmationModal
+          title="Confirm Deletion"
+          visible={this.props.deleteIndexModalVisible}
+          text={this.props.deleteIndexModalText}
+          onClose={this.props.hideDeleteIndexModal}
+          onSubmit={this.deleteIndex} />
+        <CloneIndexModal
+          visible={this.props.cloneIndexModalVisible}
+          title={this.props.cloneIndexModalTitle}
+          close={this.props.hideCloneIndexModal}
+          submit={this.cloneIndex}
+          designDocArray={this.props.availableDesignDocIds}
+          selectedDesignDoc={this.props.cloneIndexModalSelectedDesignDoc}
+          newDesignDocName={this.props.cloneIndexModalNewDesignDocName}
+          newIndexName={this.props.cloneIndexModalNewIndexName}
+          indexLabel={this.props.cloneIndexModalIndexLabel}
+          selectDesignDoc={this.props.selectDesignDoc}
+          updateNewDesignDocName={this.props.updateNewDesignDocName}
+          setNewCloneIndexName={this.props.setNewCloneIndexName} />
+      </nav>
+    );
+  }
+}
diff --git a/app/addons/documents/sidebar/helpers.js 
b/app/addons/documents/sidebar/helpers.js
new file mode 100644
index 000000000..595020860
--- /dev/null
+++ b/app/addons/documents/sidebar/helpers.js
@@ -0,0 +1,40 @@
+// 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.
+
+
+/**
+ * Represents the selected item in the sidebar, including nested nav items to 
ensure the appropriate item
+ * is visible and highlighted.
+ */
+export class SidebarItemSelection {
+
+  /**
+   * Creates a new sidebar selection.
+   *
+   * @param {string} navItem 'permissions', 'changes', 'all-docs', 'compact', 
'mango-query', 'designDoc'
+   *   (or anything thats beenextended)
+   * @param {string} [params] (optional) If you passed 'designDoc' as the 
first param. This lets you
+   *   specify which sub-item should be selected, e.g.:
+   *      Actions.selectNavItem('designDoc', { designDocName: 'my-design-doc', 
section: 'metadata' });
+   *      Actions.selectNavItem('designDoc', { designDocName: 'my-design-doc', 
section: 'Views', indexName: 'my-view' });
+   */
+  constructor(navItem, params) {
+    this.navItem = navItem;
+    if (params) {
+      const {designDocName, designDocSection, indexName} = params;
+      this.designDocName = designDocName ? designDocName : '';
+      this.designDocSection = designDocSection ? designDocSection : '';
+      this.indexName = indexName ? indexName : '';
+    }
+  }
+}
+
diff --git a/app/addons/documents/sidebar/reducers.js 
b/app/addons/documents/sidebar/reducers.js
index 63598239b..f1fa140ab 100644
--- a/app/addons/documents/sidebar/reducers.js
+++ b/app/addons/documents/sidebar/reducers.js
@@ -10,19 +10,223 @@
 // License for the specific language governing permissions and limitations 
under
 // the License.
 
-import ActionTypes from './actiontypes';
+import React from "react";
+import app from "../../../app";
+import ActionTypes from "./actiontypes";
 
 const initialState = {
-  designDocs: []
+  designDocs: new Backbone.Collection(),
+  designDocList: [],
+  selected: {
+    navItem: 'all-docs',
+    designDocName: '',
+    designDocSection: '', // 'metadata' / name of index group ("Views", etc.)
+    indexName: ''
+  },
+  loading: true,
+  toggledSections: {},
+
+  deleteIndexModalVisible: false,
+  deleteIndexModalDesignDocName: '',
+  deleteIndexModalText: '',
+  deleteIndexModalIndexName: '',
+  deleteIndexModalOnSubmit: () => {},
+
+  cloneIndexModalVisible: false,
+  cloneIndexDesignDocProp: '',
+  cloneIndexModalTitle: '',
+  cloneIndexModalSelectedDesignDoc: '',
+  cloneIndexModalNewDesignDocName: '',
+  cloneIndexModalNewIndexName: '',
+  cloneIndexModalSourceIndexName: '',
+  cloneIndexModalSourceDesignDocName: '',
+  cloneIndexModalIndexLabel: '',
+  cloneIndexModalOnSubmit: () => {}
 };
 
-export default function resultsState(state = initialState, action) {
+function setNewOptions(state, options) {
+  const newState = {
+    ...state,
+    database: options.database,
+    designDocs: options.designDocs,
+    designDocList: getDesignDocList(options.designDocs),
+    loading: false,
+  };
+  // this can be expanded in future as we need. Right now it can only set a 
top-level nav item ('all docs',
+  // 'permissions' etc.) and not a nested page
+  if (options.selectedNavItem) {
+    newState.selected = {
+      navItem: options.selectedNavItem,
+      designDocName: '',
+      designDocSection: '',
+      indexName: ''
+    };
+  }
+
+  return newState;
+}
+
+function toggleContent(state, designDoc, indexGroup) {
+  // used to toggle both design docs, and any index groups within them
+  const newState = {
+    ...state
+  };
+
+  if (!state.toggledSections[designDoc]) {
+    newState.toggledSections[designDoc] = {
+      visible: true,
+      indexGroups: {}
+    };
+    return newState;
+  }
+
+  if (indexGroup) {
+    const expanded = state.toggledSections[designDoc].indexGroups[indexGroup];
+
+    if (_.isUndefined(expanded)) {
+      newState.toggledSections[designDoc].indexGroups[indexGroup] = true;
+    } else {
+      newState.toggledSections[designDoc].indexGroups[indexGroup] = !expanded;
+    }
+    return newState;
+  }
+
+  newState.toggledSections[designDoc].visible = 
!state.toggledSections[designDoc].visible;
+
+  return newState;
+}
+
+function expandSelectedItem(state, {selectedNavItem}) {
+  const newState = {
+    ...state
+  };
+
+  if (selectedNavItem.designDocName) {
+    if (!_.has(state.toggledSections, selectedNavItem.designDocName)) {
+      newState.toggledSections[selectedNavItem.designDocName] = {
+        visible: true,
+        indexGroups: {}
+      };
+    }
+    newState.toggledSections[selectedNavItem.designDocName].visible = true;
+
+    if (selectedNavItem.designDocSection) {
+      
newState.toggledSections[selectedNavItem.designDocName].indexGroups[selectedNavItem.designDocSection]
 = true;
+    }
+  }
+  return newState;
+}
+
+function getDesignDocList (designDocs) {
+  if (!designDocs) {
+    return [];
+  }
+  let docs = designDocs.toJSON();
+  docs = _.filter(docs, (doc) => {
+    if (_.has(doc.doc, 'language')) {
+      return doc.doc.language !== 'query';
+    }
+    return true;
+  });
+
+  const ddocsList = docs.map((doc) => {
+    doc.safeId = app.utils.safeURLName(doc._id.replace(/^_design\//, ''));
+    return _.extend(doc, doc.doc);
+  });
+  return ddocsList;
+}
+
+export const getDatabase = (state) => {
+  if (state.loading) {
+    return {};
+  }
+  return state.database;
+};
+
+export default function sidebar(state = initialState, action) {
+  const { options } = action;
   switch (action.type) {
 
+    case ActionTypes.SIDEBAR_EXPAND_SELECTED_ITEM:
+      return expandSelectedItem(state, options);
+
+    case ActionTypes.SIDEBAR_NEW_OPTIONS:
+      return setNewOptions(state, options);
+
+    case ActionTypes.SIDEBAR_TOGGLE_CONTENT:
+      return toggleContent(state, action.designDoc, action.indexGroup);
+
+    case ActionTypes.SIDEBAR_FETCHING:
+      return {
+        ...state,
+        loading: true
+      };
+
+    case ActionTypes.SIDEBAR_SHOW_DELETE_INDEX_MODAL:
+      return {
+        ...state,
+        deleteIndexModalIndexName: options.indexName,
+        deleteIndexModalDesignDocName: options.designDocName,
+        deleteIndexModalVisible: true,
+        deleteIndexModalText: (
+          <div>
+            Are you sure you want to delete the 
<code>{options.indexName}</code> {options.indexLabel}?
+          </div>
+        ),
+        deleteIndexModalOnSubmit: options.onDelete
+      };
+
+
+    case ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL:
+      return {
+        ...state,
+        deleteIndexModalVisible: false
+      };
+
+    case ActionTypes.SIDEBAR_SHOW_CLONE_INDEX_MODAL:
+      return {
+        ...state,
+        cloneIndexModalIndexLabel: options.indexLabel,
+        cloneIndexModalTitle: options.cloneIndexModalTitle,
+        cloneIndexModalSourceIndexName: options.sourceIndexName,
+        cloneIndexModalSourceDesignDocName: options.sourceDesignDocName,
+        cloneIndexModalSelectedDesignDoc: '_design/' + 
options.sourceDesignDocName,
+        cloneIndexDesignDocProp: '',
+        cloneIndexModalVisible: true,
+        cloneIndexModalOnSubmit: options.onSubmit
+      };
+
+    case ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL:
+      return {
+        ...state,
+        cloneIndexModalVisible: false
+      };
+
+    case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE:
+      return {
+        ...state,
+        cloneIndexModalSelectedDesignDoc: options.value
+      };
+
+    case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED:
+      return {
+        ...state,
+        cloneIndexModalNewDesignDocName: options.value
+      };
+
+    case ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME:
+      return {
+        ...state,
+        cloneIndexModalNewIndexName: options.value
+      };
+
     case ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS:
-      return Object.assign({}, state, {
-        designDocs: action.options.designDocs
-      });
+      return {
+        ...state,
+        designDocs: options.designDocs,
+        designDocList: getDesignDocList(options.designDocs),
+        loading: false
+      };
 
     default:
       return state;
diff --git a/app/addons/documents/sidebar/sidebar.js 
b/app/addons/documents/sidebar/sidebar.js
index 60624fdb6..a20420be7 100644
--- a/app/addons/documents/sidebar/sidebar.js
+++ b/app/addons/documents/sidebar/sidebar.js
@@ -10,639 +10,12 @@
 // 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 app from "../../../app";
-import FauxtonAPI from "../../../core/api";
-import Stores from "./stores";
-import Actions from "./actions";
-import Components from "../../components/react-components";
-import ComponentsStore from "../../components/stores";
-import ComponentsActions from "../../components/actions";
-import IndexEditorActions from "../index-editor/actions";
-import IndexEditorComponents from "../index-editor/components";
-import GeneralComponents from "../../fauxton/components";
-import DocumentHelper from "../../documents/helpers";
-import { Collapse, OverlayTrigger, Popover, Modal } from "react-bootstrap";
-import "../../../../assets/js/plugins/prettify";
-
-const store = Stores.sidebarStore;
-const { DeleteDatabaseModal, LoadLines, MenuDropDown } = Components;
-const { DesignDocSelector } = IndexEditorComponents;
-const { ConfirmationModal } = GeneralComponents;
-const { deleteDbModalStore } = ComponentsStore;
-
-class MainSidebar extends React.Component {
-  static propTypes = {
-    selectedNavItem: PropTypes.string.isRequired
-  };
-
-  getNewButtonLinks = () => {  // these are links for the sidebar '+' on All 
Docs and All Design Docs
-    return DocumentHelper.getNewButtonLinks(this.props.databaseName);
-  };
-
-  buildDocLinks = () => {
-    const base = FauxtonAPI.urls('base', 'app', this.props.databaseName);
-    return FauxtonAPI.getExtensions('docLinks').map((link) => {
-      return (
-        <li key={link.url} className={this.getNavItemClass(link.url)}>
-          <a id={link.url} href={base + link.url}>{link.title}</a>
-        </li>
-      );
-    });
-  };
-
-  getNavItemClass = (navItem) => {
-    return (navItem === this.props.selectedNavItem) ? 'active' : '';
-  };
-
-  render() {
-    const docLinks = this.buildDocLinks();
-    const dbEncoded = FauxtonAPI.url.encode(this.props.databaseName);
-    const changesUrl = '#' + FauxtonAPI.urls('changes', 'app', dbEncoded, '');
-    const permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', 
dbEncoded);
-    const databaseUrl = FauxtonAPI.urls('allDocs', 'app', dbEncoded, '');
-    const mangoQueryUrl = FauxtonAPI.urls('mango', 'query-app', dbEncoded);
-    const runQueryWithMangoText = app.i18n.en_US['run-query-with-mango'];
-    const buttonLinks = this.getNewButtonLinks();
-
-    return (
-      <ul className="nav nav-list">
-        <li className={this.getNavItemClass('all-docs')}>
-          <a id="all-docs"
-            href={"#/" + databaseUrl}
-            className="toggle-view">
-            All Documents
-          </a>
-          <div id="new-all-docs-button" className="add-dropdown">
-            <MenuDropDown links={buttonLinks} />
-          </div>
-        </li>
-        <li className={this.getNavItemClass('mango-query')}>
-          <a
-            id="mango-query"
-            href={'#' + mangoQueryUrl}
-            className="toggle-view">
-            {runQueryWithMangoText}
-          </a>
-        </li>
-        <li className={this.getNavItemClass('permissions')}>
-          <a id="permissions" href={permissionsUrl}>Permissions</a>
-        </li>
-        <li className={this.getNavItemClass('changes')}>
-          <a id="changes" href={changesUrl}>Changes</a>
-        </li>
-        {docLinks}
-        <li className={this.getNavItemClass('design-docs')}>
-          <a
-            id="design-docs"
-            href={"#/" + databaseUrl + '?startkey="_design"&endkey="_design0"'}
-            className="toggle-view">
-            Design Documents
-          </a>
-          <div id="new-design-docs-button" className="add-dropdown">
-            <MenuDropDown links={buttonLinks} />
-          </div>
-        </li>
-      </ul>
-    );
-  }
-}
-
-class IndexSection extends React.Component {
-  static propTypes = {
-    urlNamespace: PropTypes.string.isRequired,
-    indexLabel: PropTypes.string.isRequired,
-    database: PropTypes.object.isRequired,
-    designDocName: PropTypes.string.isRequired,
-    items: PropTypes.array.isRequired,
-    isExpanded: PropTypes.bool.isRequired,
-    selectedIndex: PropTypes.string.isRequired,
-    onDelete: PropTypes.func.isRequired,
-    onClone: PropTypes.func.isRequired
-  };
-
-  state = {
-    placement: 'bottom'
-  };
-
-  // this dynamically changes the placement of the menu (top/bottom) to 
prevent it going offscreen and causing some
-  // unsightly shifting
-  setPlacement = (rowId) => {
-    const rowTop = document.getElementById(rowId).getBoundingClientRect().top;
-    const toggleHeight = 150; // the height of the menu overlay, arrow, view 
row
-    const placement = (rowTop + toggleHeight > window.innerHeight) ? 'top' : 
'bottom';
-    this.setState({ placement: placement });
-  };
-
-  createItems = () => {
-
-    // sort the indexes alphabetically
-    const sortedItems = this.props.items.sort();
-
-    return _.map(sortedItems, (indexName, index) => {
-      const href = FauxtonAPI.urls(this.props.urlNamespace, 'app', 
encodeURIComponent(this.props.database.id), 
encodeURIComponent(this.props.designDocName));
-      const className = (this.props.selectedIndex === indexName) ? 'active' : 
'';
-
-      return (
-        <li className={className} key={index}>
-          <a
-            id={this.props.designDocName + '_' + indexName}
-            href={"#/" + href + encodeURIComponent(indexName)}
-            className="toggle-view">
-            {indexName}
-          </a>
-          <OverlayTrigger
-            trigger="click"
-            onEnter={this.setPlacement.bind(this, this.props.designDocName + 
'_' + indexName)}
-            placement={this.state.placement}
-            rootClose={true}
-            ref={overlay => this.itemOverlay = overlay}
-            overlay={
-              <Popover id="index-menu-component-popover">
-                <ul>
-                  <li onClick={this.indexAction.bind(this, 'edit', { 
indexName: indexName, onEdit: this.props.onEdit })}>
-                    <span className="fonticon fonticon-file-code-o"></span>
-                    Edit
-                  </li>
-                  <li onClick={this.indexAction.bind(this, 'clone', { 
indexName: indexName, onClone: this.props.onClone })}>
-                    <span className="fonticon fonticon-files-o"></span>
-                    Clone
-                  </li>
-                  <li onClick={this.indexAction.bind(this, 'delete', { 
indexName: indexName, onDelete: this.props.onDelete })}>
-                    <span className="fonticon fonticon-trash"></span>
-                    Delete
-                  </li>
-                </ul>
-              </Popover>
-            }>
-            <span className="index-menu-toggle fonticon 
fonticon-wrench2"></span>
-          </OverlayTrigger>
-        </li>
-      );
-    });
-  };
-
-  indexAction = (action, params, e) => {
-    e.preventDefault();
-
-    this.itemOverlay.hide();
-
-    switch (action) {
-      case 'delete':
-        Actions.showDeleteIndexModal(params.indexName, 
this.props.designDocName, this.props.indexLabel, params.onDelete);
-        break;
-      case 'clone':
-        Actions.showCloneIndexModal(params.indexName, 
this.props.designDocName, this.props.indexLabel, params.onClone);
-        break;
-      case 'edit':
-        params.onEdit(this.props.database.id, this.props.designDocName, 
params.indexName);
-        break;
-    }
-  };
-
-  toggle = (e) => {
-    e.preventDefault();
-    this.props.toggle(this.props.designDocName, this.props.title);
-  };
-
-  render() {
-
-    // if this section has no content, omit it to prevent clutter. Otherwise 
it would show a toggle option that
-    // would hide/show nothing
-    if (this.props.items.length === 0) {
-      return null;
-    }
-
-    let toggleClassNames = 'accordion-header index-group-header';
-    let toggleBodyClassNames = 'index-list accordion-body collapse';
-    if (this.props.isExpanded) {
-      toggleClassNames += ' down';
-      toggleBodyClassNames += ' in';
-    }
-
-    const title = this.props.title;
-    const designDocName = this.props.designDocName;
-    const linkId = "nav-design-function-" + designDocName + 
this.props.selector;
-
-    return (
-      <li id={linkId}>
-        <a className={toggleClassNames} data-toggle="collapse" 
onClick={this.toggle}>
-          <div className="fonticon-play"></div>
-          {title}
-        </a>
-        <Collapse in={this.props.isExpanded}>
-          <ul className={toggleBodyClassNames}>
-            {this.createItems()}
-          </ul>
-        </Collapse>
-      </li>
-    );
-  }
-}
-
-class DesignDoc extends React.Component {
-  static propTypes = {
-    database: PropTypes.object.isRequired,
-    sidebarListTypes: PropTypes.array.isRequired,
-    isExpanded: PropTypes.bool.isRequired,
-    selectedNavInfo: PropTypes.object.isRequired,
-    toggledSections: PropTypes.object.isRequired,
-    designDocName:  PropTypes.string.isRequired
-  };
-
-  state = {
-    updatedSidebarListTypes: this.props.sidebarListTypes
-  };
-
-  UNSAFE_componentWillMount() {
-    if (_.isEmpty(this.state.updatedSidebarListTypes) ||
-      (_.has(this.state.updatedSidebarListTypes[0], 'selector') && 
this.state.updatedSidebarListTypes[0].selector !== 'views')) {
-
-      const newList = this.state.updatedSidebarListTypes;
-      newList.unshift({
-        selector: 'views',
-        name: 'Views',
-        urlNamespace: 'view',
-        indexLabel: 'view',
-        onDelete: IndexEditorActions.deleteView,
-        onClone: IndexEditorActions.cloneView,
-        onEdit: IndexEditorActions.gotoEditViewPage
-      });
-      this.setState({ updatedSidebarListTypes: newList });
-    }
-  }
-
-  indexList = () => {
-    return _.map(this.state.updatedSidebarListTypes, (index, key) => {
-      const expanded = _.has(this.props.toggledSections, index.name) && 
this.props.toggledSections[index.name];
-
-      // if an index in this list is selected, pass that down
-      let selectedIndex = '';
-      if (this.props.selectedNavInfo.designDocSection === index.name) {
-        selectedIndex = this.props.selectedNavInfo.indexName;
-      }
-
-      return (
-        <IndexSection
-          icon={index.icon}
-          isExpanded={expanded}
-          urlNamespace={index.urlNamespace}
-          indexLabel={index.indexLabel}
-          onEdit={index.onEdit}
-          onDelete={index.onDelete}
-          onClone={index.onClone}
-          selectedIndex={selectedIndex}
-          toggle={this.props.toggle}
-          database={this.props.database}
-          designDocName={this.props.designDocName}
-          key={key}
-          title={index.name}
-          selector={index.selector}
-          items={_.keys(this.props.designDoc[index.selector])} />
-      );
-    });
-  };
-
-  toggle = (e) => {
-    e.preventDefault();
-    this.props.toggle(this.props.designDocName);
-  };
-
-  getNewButtonLinks = () => {
-    const newUrlPrefix = FauxtonAPI.urls('databaseBaseURL', 'app', 
encodeURIComponent(this.props.database.id));
-    const designDocName = this.props.designDocName;
-
-    const addNewLinks = _.reduce(FauxtonAPI.getExtensions('sidebar:links'), 
function (menuLinks, link) {
-      menuLinks.push({
-        title: link.title,
-        url: '#' + newUrlPrefix + '/' + link.url + '/' + 
encodeURIComponent(designDocName),
-        icon: 'fonticon-plus-circled'
-      });
-      return menuLinks;
-    }, [{
-      title: 'New View',
-      url: '#' + FauxtonAPI.urls('new', 'addView', 
encodeURIComponent(this.props.database.id), encodeURIComponent(designDocName)),
-      icon: 'fonticon-plus-circled'
-    }]);
-
-    return [{
-      title: 'Add New',
-      links: addNewLinks
-    }];
-  };
-
-  render () {
-    const buttonLinks = this.getNewButtonLinks();
-    let toggleClassNames = 'design-doc-section accordion-header';
-    let toggleBodyClassNames = 'design-doc-body accordion-body collapse';
-
-    if (this.props.isExpanded) {
-      toggleClassNames += ' down';
-      toggleBodyClassNames += ' in';
-    }
-    const designDocName = this.props.designDocName;
-    const designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', 
this.props.database.id, designDocName);
-    const metadataRowClass = (this.props.selectedNavInfo.designDocSection === 
'metadata') ? 'active' : '';
-
-    return (
-      <li className="nav-header">
-        <div id={"sidebar-tab-" + designDocName} className={toggleClassNames}>
-          <div id={"nav-header-" + designDocName} onClick={this.toggle} 
className='accordion-list-item'>
-            <div className="fonticon-play"></div>
-            <p className='design-doc-name'>
-              <span title={'_design/' + designDocName}>{designDocName}</span>
-            </p>
-          </div>
-          <div className='new-button add-dropdown'>
-            <MenuDropDown links={buttonLinks} />
-          </div>
-        </div>
-        <Collapse in={this.props.isExpanded}>
-          <ul className={toggleBodyClassNames} id={this.props.designDocName}>
-            <li className={metadataRowClass}>
-              <a href={"#/" + designDocMetaUrl} className="toggle-view 
accordion-header">
-                Metadata
-              </a>
-            </li>
-            {this.indexList()}
-          </ul>
-        </Collapse>
-      </li>
-    );
-  }
-}
-
-class DesignDocList extends React.Component {
-  UNSAFE_componentWillMount() {
-    const list = FauxtonAPI.getExtensions('sidebar:list');
-    this.sidebarListTypes = _.isUndefined(list) ? [] : list;
-  }
-
-  designDocList = () => {
-    return _.map(this.props.designDocs, (designDoc, key) => {
-      const ddName = decodeURIComponent(designDoc.safeId);
-
-      // only pass down the selected nav info and toggle info if they're 
relevant for this particular design doc
-      let expanded = false,
-          toggledSections = {};
-      if (_.has(this.props.toggledSections, ddName)) {
-        expanded = this.props.toggledSections[ddName].visible;
-        toggledSections = this.props.toggledSections[ddName].indexGroups;
-      }
-
-      let selectedNavInfo = {};
-      if (this.props.selectedNav.navItem === 'designDoc' && 
this.props.selectedNav.designDocName === ddName) {
-        selectedNavInfo = this.props.selectedNav;
-      }
-
-      return (
-        <DesignDoc
-          toggle={this.props.toggle}
-          sidebarListTypes={this.sidebarListTypes}
-          isExpanded={expanded}
-          toggledSections={toggledSections}
-          selectedNavInfo={selectedNavInfo}
-          key={key}
-          designDoc={designDoc}
-          designDocName={ddName}
-          database={this.props.database} />
-      );
-    });
-  };
-
-  render() {
-    return (
-      <ul className="nav nav-list">
-        {this.designDocList()}
-      </ul>
-    );
-  }
-}
-
-class SidebarController extends React.Component {
-  getStoreState = () => {
-    return {
-      database: store.getDatabase(),
-      selectedNav: store.getSelected(),
-      designDocs: store.getDesignDocs(),
-      designDocList: store.getDesignDocList(),
-      availableDesignDocIds: store.getAvailableDesignDocs(),
-      toggledSections: store.getToggledSections(),
-      isLoading: store.isLoading(),
-      deleteDbModalProperties: deleteDbModalStore.getShowDeleteDatabaseModal(),
-
-      deleteIndexModalVisible: store.isDeleteIndexModalVisible(),
-      deleteIndexModalText: store.getDeleteIndexModalText(),
-      deleteIndexModalOnSubmit: store.getDeleteIndexModalOnSubmit(),
-      deleteIndexModalIndexName: store.getDeleteIndexModalIndexName(),
-      deleteIndexModalDesignDoc: store.getDeleteIndexDesignDoc(),
-
-      cloneIndexModalVisible: store.isCloneIndexModalVisible(),
-      cloneIndexModalTitle: store.getCloneIndexModalTitle(),
-      cloneIndexModalSelectedDesignDoc: 
store.getCloneIndexModalSelectedDesignDoc(),
-      cloneIndexModalNewDesignDocName: 
store.getCloneIndexModalNewDesignDocName(),
-      cloneIndexModalOnSubmit: store.getCloneIndexModalOnSubmit(),
-      cloneIndexDesignDocProp: store.getCloneIndexDesignDocProp(),
-      cloneIndexModalNewIndexName: store.getCloneIndexModalNewIndexName(),
-      cloneIndexSourceIndexName: store.getCloneIndexModalSourceIndexName(),
-      cloneIndexSourceDesignDocName: 
store.getCloneIndexModalSourceDesignDocName(),
-      cloneIndexModalIndexLabel: store.getCloneIndexModalIndexLabel()
-    };
-  };
-
-  componentDidMount() {
-    store.on('change', this.onChange, this);
-    deleteDbModalStore.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    store.off('change', this.onChange);
-    deleteDbModalStore.off('change', this.onChange, this);
-  }
-
-  onChange = () => {
-
-    const newState = this.getStoreState();
-    // Workaround to signal Redux store that the design doc list was updated
-    // which is currently required by QueryOptionsContainer
-    // It should be removed once Sidebar components are refactored to use Redux
-    if (this.props.reduxUpdatedDesignDocList) {
-      this.props.reduxUpdatedDesignDocList(newState.designDocList);
-    }
-
-    this.setState(newState);
-  };
-
-  showDeleteDatabaseModal = (payload) => {
-    ComponentsActions.showDeleteDatabaseModal(payload);
-  };
-
-  // handles deleting of any index regardless of type. The delete handler and 
all relevant info is set when the user
-  // clicks the delete action for a particular index
-  deleteIndex = () => {
-
-    // if the user is currently on the index that's being deleted, pass that 
info along to the delete handler. That can
-    // be used to redirect the user to somewhere appropriate
-    const isOnIndex = this.state.selectedNav.navItem === 'designDoc' &&
-      ('_design/' + this.state.selectedNav.designDocName) === 
this.state.deleteIndexModalDesignDoc.id &&
-      this.state.selectedNav.indexName === 
this.state.deleteIndexModalIndexName;
-
-    this.state.deleteIndexModalOnSubmit({
-      isOnIndex: isOnIndex,
-      indexName: this.state.deleteIndexModalIndexName,
-      designDoc: this.state.deleteIndexModalDesignDoc,
-      designDocs: this.state.designDocs,
-      database: this.state.database
-    });
-  };
-
-  cloneIndex = () => {
-    this.state.cloneIndexModalOnSubmit({
-      sourceIndexName: this.state.cloneIndexSourceIndexName,
-      sourceDesignDocName: this.state.cloneIndexSourceDesignDocName,
-      targetDesignDocName: this.state.cloneIndexModalSelectedDesignDoc,
-      newDesignDocName: this.state.cloneIndexModalNewDesignDocName,
-      newIndexName: this.state.cloneIndexModalNewIndexName,
-      designDocs: this.state.designDocs,
-      database: this.state.database,
-      onComplete: Actions.hideCloneIndexModal
-    });
-  };
-
-  state = this.getStoreState();
-
-  render() {
-    if (this.state.isLoading) {
-      return <LoadLines />;
-    }
-
-    return (
-      <nav className="sidenav">
-        <MainSidebar
-          selectedNavItem={this.state.selectedNav.navItem}
-          databaseName={this.state.database.id} />
-        <DesignDocList
-          selectedNav={this.state.selectedNav}
-          toggle={Actions.toggleContent}
-          toggledSections={this.state.toggledSections}
-          designDocs={this.state.designDocList}
-          database={this.state.database} />
-        <DeleteDatabaseModal
-          showHide={this.showDeleteDatabaseModal}
-          modalProps={this.state.deleteDbModalProperties} />
-
-        {/* the delete and clone index modals handle all index types, hence 
the props all being pulled from the store */}
-        <ConfirmationModal
-          title="Confirm Deletion"
-          visible={this.state.deleteIndexModalVisible}
-          text={this.state.deleteIndexModalText}
-          onClose={Actions.hideDeleteIndexModal}
-          onSubmit={this.deleteIndex} />
-        <CloneIndexModal
-          visible={this.state.cloneIndexModalVisible}
-          title={this.state.cloneIndexModalTitle}
-          close={Actions.hideCloneIndexModal}
-          submit={this.cloneIndex}
-          designDocArray={this.state.availableDesignDocIds}
-          selectedDesignDoc={this.state.cloneIndexModalSelectedDesignDoc}
-          newDesignDocName={this.state.cloneIndexModalNewDesignDocName}
-          newIndexName={this.state.cloneIndexModalNewIndexName}
-          indexLabel={this.state.cloneIndexModalIndexLabel} />
-      </nav>
-    );
-  }
-}
-
-class CloneIndexModal extends React.Component {
-  static propTypes = {
-    visible: PropTypes.bool.isRequired,
-    title: PropTypes.string,
-    close: PropTypes.func.isRequired,
-    submit: PropTypes.func.isRequired,
-    designDocArray: PropTypes.array.isRequired,
-    selectedDesignDoc: PropTypes.string.isRequired,
-    newDesignDocName: PropTypes.string.isRequired,
-    newIndexName: PropTypes.string.isRequired,
-    indexLabel: PropTypes.string.isRequired
-  };
-
-  static defaultProps = {
-    title: 'Clone Index',
-    visible: false
-  };
-
-  submit = () => {
-    if (!this.designDocSelector.validate()) {
-      return;
-    }
-    if (this.props.newIndexName === '') {
-      FauxtonAPI.addNotification({
-        msg: 'Please enter the new index name.',
-        type: 'error',
-        clear: true
-      });
-      return;
-    }
-    this.props.submit();
-  };
-
-  close = (e) => {
-    if (e) {
-      e.preventDefault();
-    }
-    this.props.close();
-  };
-
-  setNewIndexName = (e) => {
-    Actions.setNewCloneIndexName(e.target.value);
-  };
-
-  render() {
-    return (
-      <Modal dialogClassName="clone-index-modal" show={this.props.visible} 
onHide={this.close}>
-        <Modal.Header closeButton={true}>
-          <Modal.Title>{this.props.title}</Modal.Title>
-        </Modal.Header>
-        <Modal.Body>
-
-          <form className="form" method="post" onSubmit={this.submit}>
-            <p>
-              Select the design document where the cloned 
{this.props.indexLabel} will be created, and then enter
-              a name for the cloned {this.props.indexLabel}.
-            </p>
-
-            <div className="row">
-              <DesignDocSelector
-                ref={node => this.designDocSelector = node}
-                designDocList={this.props.designDocArray}
-                selectedDesignDocName={this.props.selectedDesignDoc}
-                newDesignDocName={this.props.newDesignDocName}
-                onSelectDesignDoc={Actions.selectDesignDoc}
-                onChangeNewDesignDocName={Actions.updateNewDesignDocName} />
-            </div>
-
-            <div className="clone-index-name-row">
-              <label className="new-index-title-label" 
htmlFor="new-index-name">{this.props.indexLabel} Name</label>
-              <input type="text" id="new-index-name" 
value={this.props.newIndexName} onChange={this.setNewIndexName}
-                placeholder="New view name" />
-            </div>
-          </form>
-
-        </Modal.Body>
-        <Modal.Footer>
-          <a href="#" className="cancel-link" onClick={this.close} 
data-bypass="true">Cancel</a>
-          <button onClick={this.submit} data-bypass="true" className="btn 
btn-primary save">
-            <i className="icon fonticon-ok-circled" /> Clone 
{this.props.indexLabel}</button>
-        </Modal.Footer>
-      </Modal>
-    );
-  }
-}
+import CloneIndexModal from './components/CloneIndexModal';
+import DesignDoc from './components/DesignDoc';
+import SidebarController from './components/SidebarController';
 
 export default {
-  SidebarController: SidebarController,
-  DesignDoc: DesignDoc,
-  CloneIndexModal: CloneIndexModal
+  SidebarController,
+  DesignDoc,
+  CloneIndexModal
 };
diff --git a/app/addons/documents/sidebar/stores.js 
b/app/addons/documents/sidebar/stores.js
deleted file mode 100644
index 37ece8646..000000000
--- a/app/addons/documents/sidebar/stores.js
+++ /dev/null
@@ -1,337 +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 app from "../../../app";
-import FauxtonAPI from "../../../core/api";
-import React from "react";
-import ActionTypes from "./actiontypes";
-var Stores = {};
-
-Stores.SidebarStore = FauxtonAPI.Store.extend({
-
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._designDocs = new Backbone.Collection();
-    this._selected = {
-      navItem: 'all-docs',
-      designDocName: '',
-      designDocSection: '', // 'metadata' / name of index group ("Views", etc.)
-      indexName: ''
-    };
-    this._loading = true;
-    this._toggledSections = {};
-
-    this._deleteIndexModalVisible = false;
-    this._deleteIndexModalDesignDocName = '';
-    this._deleteIndexModalText = '';
-    this._deleteIndexModalIndexName = '';
-    this._deleteIndexModalOnSubmit = function () { };
-
-    this._cloneIndexModalVisible = false;
-    this._cloneIndexDesignDocProp = '';
-    this._cloneIndexModalTitle = '';
-    this._cloneIndexModalSelectedDesignDoc = '';
-    this._cloneIndexModalNewDesignDocName = '';
-    this._cloneIndexModalNewIndexName = '';
-    this._cloneIndexModalIndexLabel = '';
-    this._cloneIndexModalOnSubmit = function () { };
-  },
-
-  newOptions: function (options) {
-    this._database = options.database;
-    this._designDocs = options.designDocs;
-    this._loading = false;
-
-    // this can be expanded in future as we need. Right now it can only set a 
top-level nav item ('all docs',
-    // 'permissions' etc.) and not a nested page
-    if (options.selectedNavItem) {
-      this._selected = {
-        navItem: options.selectedNavItem,
-        designDocName: '',
-        designDocSection: '',
-        indexName: ''
-      };
-    }
-  },
-
-  updatedDesignDocs: function (designDocs) {
-    this._designDocs = designDocs;
-  },
-
-  isDeleteIndexModalVisible: function () {
-    return this._deleteIndexModalVisible;
-  },
-
-  getDeleteIndexModalText: function () {
-    return this._deleteIndexModalText;
-  },
-
-  getDeleteIndexModalOnSubmit: function () {
-    return this._deleteIndexModalOnSubmit;
-  },
-
-  isLoading: function () {
-    return this._loading;
-  },
-
-  getDatabase: function () {
-    if (this.isLoading()) {
-      return {};
-    }
-    return this._database;
-  },
-
-  // used to toggle both design docs, and any index groups within them
-  toggleContent: function (designDoc, indexGroup) {
-    if (!this._toggledSections[designDoc]) {
-      this._toggledSections[designDoc] = {
-        visible: true,
-        indexGroups: {}
-      };
-      return;
-    }
-
-    if (indexGroup) {
-      return this.toggleIndexGroup(designDoc, indexGroup);
-    }
-
-    this._toggledSections[designDoc].visible = 
!this._toggledSections[designDoc].visible;
-  },
-
-  toggleIndexGroup: function (designDoc, indexGroup) {
-    var expanded = this._toggledSections[designDoc].indexGroups[indexGroup];
-
-    if (_.isUndefined(expanded)) {
-      this._toggledSections[designDoc].indexGroups[indexGroup] = true;
-      return;
-    }
-
-    this._toggledSections[designDoc].indexGroups[indexGroup] = !expanded;
-  },
-
-  isVisible: function (designDoc, indexGroup) {
-    if (!this._toggledSections[designDoc]) {
-      return false;
-    }
-    if (indexGroup) {
-      return this._toggledSections[designDoc].indexGroups[indexGroup];
-    }
-    return this._toggledSections[designDoc].visible;
-  },
-
-  getSelected: function () {
-    return this._selected;
-  },
-
-  setSelected: function (params) {
-    this._selected = {
-      navItem: params.navItem,
-      designDocName: params.designDocName,
-      designDocSection: params.designDocSection,
-      indexName: params.indexName
-    };
-
-    if (params.designDocName) {
-      if (!_.has(this._toggledSections, params.designDocName)) {
-        this._toggledSections[params.designDocName] = { visible: true, 
indexGroups: {} };
-      }
-      this._toggledSections[params.designDocName].visible = true;
-
-      if (params.designDocSection) {
-        
this._toggledSections[params.designDocName].indexGroups[params.designDocSection]
 = true;
-      }
-    }
-  },
-
-  getToggledSections: function () {
-    return this._toggledSections;
-  },
-
-  getDatabaseName: function () {
-    if (this.isLoading()) {
-      return '';
-    }
-    return this._database.safeID();
-  },
-
-  getDesignDocs: function () {
-    return this._designDocs;
-  },
-
-  // returns a simple array of design doc IDs
-  getAvailableDesignDocs: function () {
-    var availableDocs = this.getDesignDocs().filter(function (doc) {
-      return !doc.isMangoDoc();
-    });
-    return _.map(availableDocs, function (doc) {
-      return doc.id;
-    });
-  },
-
-  getDesignDocList: function () {
-    if (this.isLoading()) {
-      return {};
-    }
-    var docs = this._designDocs.toJSON();
-
-    docs = _.filter(docs, function (doc) {
-      if (_.has(doc.doc, 'language')) {
-        return doc.doc.language !== 'query';
-      }
-      return true;
-    });
-
-    return docs.map(function (doc) {
-      doc.safeId = app.utils.safeURLName(doc._id.replace(/^_design\//, ""));
-      return _.extend(doc, doc.doc);
-    });
-  },
-
-  showDeleteIndexModal: function (params) {
-    this._deleteIndexModalIndexName = params.indexName;
-    this._deleteIndexModalDesignDocName = params.designDocName;
-    this._deleteIndexModalVisible = true;
-    this._deleteIndexModalText = (<div>Are you sure you want to delete the 
<code>{this._deleteIndexModalIndexName}</code> {params.indexLabel}?</div>);
-    this._deleteIndexModalOnSubmit = params.onDelete;
-  },
-
-  getDeleteIndexModalIndexName: function () {
-    return this._deleteIndexModalIndexName;
-  },
-
-  getDeleteIndexDesignDoc: function () {
-    var designDoc = this._designDocs.find((ddoc) => {
-      return '_design/' + this._deleteIndexModalDesignDocName === ddoc.id;
-    });
-
-    return (designDoc) ? designDoc.dDocModel() : null;
-  },
-
-  isCloneIndexModalVisible: function () {
-    return this._cloneIndexModalVisible;
-  },
-
-  getCloneIndexModalTitle: function () {
-    return this._cloneIndexModalTitle;
-  },
-
-  showCloneIndexModal: function (params) {
-    this._cloneIndexModalIndexLabel = params.indexLabel;
-    this._cloneIndexModalTitle = params.cloneIndexModalTitle;
-    this._cloneIndexModalSourceIndexName = params.sourceIndexName;
-    this._cloneIndexModalSourceDesignDocName = params.sourceDesignDocName;
-    this._cloneIndexModalSelectedDesignDoc = '_design/' + 
params.sourceDesignDocName;
-    this._cloneIndexDesignDocProp = '';
-    this._cloneIndexModalVisible = true;
-    this._cloneIndexModalOnSubmit = params.onSubmit;
-  },
-
-  getCloneIndexModalIndexLabel: function () {
-    return this._cloneIndexModalIndexLabel;
-  },
-
-  getCloneIndexModalOnSubmit: function () {
-    return this._cloneIndexModalOnSubmit;
-  },
-
-  getCloneIndexModalSourceIndexName: function () {
-    return this._cloneIndexModalSourceIndexName;
-  },
-
-  getCloneIndexModalSourceDesignDocName: function () {
-    return this._cloneIndexModalSourceDesignDocName;
-  },
-
-  getCloneIndexDesignDocProp: function () {
-    return this._cloneIndexDesignDocProp;
-  },
-
-  getCloneIndexModalSelectedDesignDoc: function () {
-    return this._cloneIndexModalSelectedDesignDoc;
-  },
-
-  getCloneIndexModalNewDesignDocName: function () {
-    return this._cloneIndexModalNewDesignDocName;
-  },
-
-  getCloneIndexModalNewIndexName: function () {
-    return this._cloneIndexModalNewIndexName;
-  },
-
-  dispatch: function (action) {
-    switch (action.type) {
-      case ActionTypes.SIDEBAR_SET_SELECTED_NAV_ITEM:
-        this.setSelected(action.options);
-        break;
-
-      case ActionTypes.SIDEBAR_NEW_OPTIONS:
-        this.newOptions(action.options);
-        break;
-
-      case ActionTypes.SIDEBAR_TOGGLE_CONTENT:
-        this.toggleContent(action.designDoc, action.indexGroup);
-        break;
-
-      case ActionTypes.SIDEBAR_FETCHING:
-        this._loading = true;
-        break;
-
-      case ActionTypes.SIDEBAR_SHOW_DELETE_INDEX_MODAL:
-        this.showDeleteIndexModal(action.options);
-        break;
-
-      case ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL:
-        this._deleteIndexModalVisible = false;
-        break;
-
-      case ActionTypes.SIDEBAR_SHOW_CLONE_INDEX_MODAL:
-        this.showCloneIndexModal(action.options);
-        break;
-
-      case ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL:
-        this._cloneIndexModalVisible = false;
-        break;
-
-      case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE:
-        this._cloneIndexModalSelectedDesignDoc = action.options.value;
-        break;
-
-      case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED:
-        this._cloneIndexModalNewDesignDocName = action.options.value;
-        break;
-
-      case ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME:
-        this._cloneIndexModalNewIndexName = action.options.value;
-        break;
-
-      case ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS:
-        this.updatedDesignDocs(action.options.designDocs);
-        this._loading = false;
-        break;
-
-      default:
-        return;
-      // do nothing
-    }
-
-    this.triggerChange();
-  }
-
-});
-
-Stores.sidebarStore = new Stores.SidebarStore();
-Stores.sidebarStore.dispatchToken = 
FauxtonAPI.dispatcher.register(Stores.sidebarStore.dispatch.bind(Stores.sidebarStore));
-
-export default Stores;
diff --git a/app/addons/permissions/layout.js b/app/addons/permissions/layout.js
index 256991157..353473386 100644
--- a/app/addons/permissions/layout.js
+++ b/app/addons/permissions/layout.js
@@ -13,9 +13,11 @@
 import React from 'react';
 import {TabsSidebarHeader} from '../documents/layouts';
 import PermissionsContainer from './container/PermissionsContainer';
-import SidebarComponents from "../documents/sidebar/sidebar";
+import SidebarControllerContainer from 
"../documents/sidebar/SidebarControllerContainer";
+import {SidebarItemSelection} from '../documents/sidebar/helpers';
 
 export const PermissionsLayout = ({docURL, database, endpoint, dbName, 
dropDownLinks}) => {
+  const selectedNavItem = new SidebarItemSelection('permissions');
   return (
     <div id="dashboard" className="with-sidebar">
       <TabsSidebarHeader
@@ -29,7 +31,7 @@ export const PermissionsLayout = ({docURL, database, 
endpoint, dbName, dropDownL
       />
       <div className="with-sidebar tabs-with-sidebar content-area">
         <aside id="sidebar-content" className="scrollable">
-          <SidebarComponents.SidebarController />
+          <SidebarControllerContainer selectedNavItem={selectedNavItem}/>
         </aside>
         <section id="dashboard-content" className="flex-layout flex-col">
           <PermissionsContainer url={endpoint} />
diff --git a/app/main.js b/app/main.js
index 1176692ac..4089e77b4 100644
--- a/app/main.js
+++ b/app/main.js
@@ -28,6 +28,12 @@ const store = createStore(
   combineReducers(FauxtonAPI.reducers),
   applyMiddleware(...FauxtonAPI.middlewares)
 );
+FauxtonAPI.reduxDispatch = (action) => {
+  store.dispatch(action);
+};
+FauxtonAPI.reduxState = () => {
+  return store.getState();
+};
 
 app.addons = LoadAddons;
 FauxtonAPI.router = app.router = new FauxtonAPI.Router(app.addons);


 

----------------------------------------------------------------
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:
[email protected]


With regards,
Apache Git Services

Reply via email to