couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gar...@apache.org
Subject [10/27] fauxton commit: updated refs/heads/master to 0ca35da
Date Tue, 31 May 2016 07:58:38 GMT
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/shared-resources.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/shared-resources.js b/app/addons/documents/shared-resources.js
index 0dc3346..3499ae9 100644
--- a/app/addons/documents/shared-resources.js
+++ b/app/addons/documents/shared-resources.js
@@ -10,300 +10,297 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../app',
-  '../../core/api',
-  '../../../assets/js/plugins/cloudant.pagingcollection'
-], function (app, FauxtonAPI, PagingCollection) {
-
-  // defined here because this is contains the base resources used throughout the addon and outside,
-  // so it's the first code that gets run
-  var Documents = FauxtonAPI.addon();
-
-
-  Documents.Doc = FauxtonAPI.Model.extend({
-    idAttribute: "_id",
-    documentation: function () {
-      return FauxtonAPI.constants.DOC_URLS.GENERAL;
-    },
-
-    url: function (context) {
-      if (context === undefined) {
-        context = 'server';
-      }
+import app from "../../app";
+import FauxtonAPI from "../../core/api";
+import PagingCollection from "../../../assets/js/plugins/cloudant.pagingcollection";
 
-      // new without id make a POST to the DB and not a PUT on a DB
-      let id = this.safeID();
-      if (!id) {
-        id = '';
-      }
+// defined here because this is contains the base resources used throughout the addon and outside,
+// so it's the first code that gets run
+var Documents = FauxtonAPI.addon();
 
-      const query = this.fetchConflicts ? '?conflicts=true' : '';
-      return FauxtonAPI.urls('document', context, this.getDatabase().safeID(), id, query);
-    },
 
-    initialize: function (_attrs, options) {
-      if (this.collection && this.collection.database) {
-        this.database = this.collection.database;
-      } else if (options.database) {
-        this.database = options.database;
-      }
+Documents.Doc = FauxtonAPI.Model.extend({
+  idAttribute: "_id",
+  documentation: function () {
+    return FauxtonAPI.constants.DOC_URLS.GENERAL;
+  },
 
-      if (options.fetchConflicts) {
-        this.fetchConflicts = true;
-      }
-    },
+  url: function (context) {
+    if (context === undefined) {
+      context = 'server';
+    }
 
-    // HACK: the doc needs to know about the database, but it may be
-    // set directly or indirectly in all docs
-    getDatabase: function () {
-      return this.database ? this.database : this.collection.database;
-    },
+    // new without id make a POST to the DB and not a PUT on a DB
+    let id = this.safeID();
+    if (!id) {
+      id = '';
+    }
 
-    validate: function (attrs, options) {
-      if (this.id && this.id !== attrs._id && this.get('_rev') ) {
-        return "Cannot change a documents id.";
-      }
-    },
+    const query = this.fetchConflicts ? '?conflicts=true' : '';
+    return FauxtonAPI.urls('document', context, this.getDatabase().safeID(), id, query);
+  },
 
-    docType: function () {
-      return app.utils.getDocTypeFromId(this.id);
-    },
+  initialize: function (_attrs, options) {
+    if (this.collection && this.collection.database) {
+      this.database = this.collection.database;
+    } else if (options.database) {
+      this.database = options.database;
+    }
 
-    // @deprecated, see isJSONDocBulkDeletable
-    isBulkDeletable: function () {
-      return !!this.id && !!this.get('_rev');
-    },
+    if (options.fetchConflicts) {
+      this.fetchConflicts = true;
+    }
+  },
 
-    isDeletable: function () {
-      return !!this.id;
-    },
+  // HACK: the doc needs to know about the database, but it may be
+  // set directly or indirectly in all docs
+  getDatabase: function () {
+    return this.database ? this.database : this.collection.database;
+  },
 
-    isFromView: function () {
-      return !this.id;
-    },
+  validate: function (attrs, options) {
+    if (this.id && this.id !== attrs._id && this.get('_rev') ) {
+      return "Cannot change a documents id.";
+    }
+  },
 
-    isMangoDoc: function () {
-      if (!this.isDdoc()) return false;
-      if (this.get('language') === 'query') {
-        return true;
-      }
+  docType: function () {
+    return app.utils.getDocTypeFromId(this.id);
+  },
 
-      if (this.get('doc') && this.get('doc').language === 'query') {
-        return true;
-      }
+  // @deprecated, see isJSONDocBulkDeletable
+  isBulkDeletable: function () {
+    return !!this.id && !!this.get('_rev');
+  },
 
-      return false;
-    },
+  isDeletable: function () {
+    return !!this.id;
+  },
 
-    isDdoc: function () {
-      return this.docType() === "design doc";
-    },
+  isFromView: function () {
+    return !this.id;
+  },
 
-    setDdocView: function (view, map, reduce) {
-      if (!this.isDdoc()) {
-        return false;
-      }
+  isMangoDoc: function () {
+    if (!this.isDdoc()) return false;
+    if (this.get('language') === 'query') {
+      return true;
+    }
 
-      var views = this.get('views'),
-          tempView = views[view] || {};
+    if (this.get('doc') && this.get('doc').language === 'query') {
+      return true;
+    }
 
-      if (reduce) {
-        tempView.reduce = reduce;
-      } else {
-        delete tempView.reduce;
-      }
-      tempView.map = map;
+    return false;
+  },
 
-      views[view] = tempView;
-      this.set({views: views});
+  isDdoc: function () {
+    return this.docType() === "design doc";
+  },
 
-      return true;
-    },
+  setDdocView: function (view, map, reduce) {
+    if (!this.isDdoc()) {
+      return false;
+    }
 
-    removeDdocView: function (viewName) {
-      if (!this.isDdoc()) return false;
-      var views = this.get('views');
+    var views = this.get('views'),
+        tempView = views[view] || {};
 
-      delete views[viewName];
-      this.set({views: views});
-    },
+    if (reduce) {
+      tempView.reduce = reduce;
+    } else {
+      delete tempView.reduce;
+    }
+    tempView.map = map;
 
-    dDocModel: function () {
-      if (!this.isDdoc()) return false;
-      var doc = this.get('doc');
+    views[view] = tempView;
+    this.set({views: views});
 
-      if (doc) {
-        doc._rev = this.get('_rev');
-        return new Documents.Doc(doc, {database: this.database});
-      }
+    return true;
+  },
 
-      return this;
-    },
-
-    safeID: function () {
-      return app.utils.getSafeIdForDoc(this.id);
-    },
-
-    destroy: function () {
-      var url = this.url() + "?rev=" + this.get('_rev');
-      return $.ajax({
-        url: url,
-        dataType: 'json',
-        type: 'DELETE'
-      });
-    },
-
-    parse: function (resp) {
-      if (resp.rev) {
-        resp._rev = resp.rev;
-        delete resp.rev;
-      }
-      if (resp.id) {
-        if (_.isUndefined(this.id)) {
-          resp._id = resp.id;
-        }
-      }
+  removeDdocView: function (viewName) {
+    if (!this.isDdoc()) return false;
+    var views = this.get('views');
 
-      if (resp.ok) {
-        delete resp.id;
-        delete resp.ok;
+    delete views[viewName];
+    this.set({views: views});
+  },
+
+  dDocModel: function () {
+    if (!this.isDdoc()) return false;
+    var doc = this.get('doc');
+
+    if (doc) {
+      doc._rev = this.get('_rev');
+      return new Documents.Doc(doc, {database: this.database});
+    }
+
+    return this;
+  },
+
+  safeID: function () {
+    return app.utils.getSafeIdForDoc(this.id);
+  },
+
+  destroy: function () {
+    var url = this.url() + "?rev=" + this.get('_rev');
+    return $.ajax({
+      url: url,
+      dataType: 'json',
+      type: 'DELETE'
+    });
+  },
+
+  parse: function (resp) {
+    if (resp.rev) {
+      resp._rev = resp.rev;
+      delete resp.rev;
+    }
+    if (resp.id) {
+      if (_.isUndefined(this.id)) {
+        resp._id = resp.id;
       }
+    }
 
-      return resp;
-    },
+    if (resp.ok) {
+      delete resp.id;
+      delete resp.ok;
+    }
 
-    prettyJSON: function () {
-      var data = this.get("doc") ? this.get("doc") : this.attributes;
+    return resp;
+  },
 
-      return JSON.stringify(data, null, "  ");
-    },
+  prettyJSON: function () {
+    var data = this.get("doc") ? this.get("doc") : this.attributes;
 
-    copy: function (copyId) {
-      return $.ajax({
-        type: 'COPY',
-        url: '/' + this.database.safeID() + '/' + this.safeID(),
-        headers: {Destination: copyId}
-      });
-    },
+    return JSON.stringify(data, null, "  ");
+  },
 
-    isNewDoc: function () {
-      return this.get('_rev') ? false : true;
-    }
-  });
+  copy: function (copyId) {
+    return $.ajax({
+      type: 'COPY',
+      url: '/' + this.database.safeID() + '/' + this.safeID(),
+      headers: {Destination: copyId}
+    });
+  },
 
+  isNewDoc: function () {
+    return this.get('_rev') ? false : true;
+  }
+});
 
-  Documents.AllDocs = PagingCollection.extend({
-    model: Documents.Doc,
-    documentation: function () {
-      return FauxtonAPI.constants.DOC_URLS.GENERAL;
-    },
-    initialize: function (_models, options) {
-      this.viewMeta = options.viewMeta;
-      this.database = options.database;
-      this.params = _.clone(options.params);
 
-      this.on("remove", this.decrementTotalRows, this);
-      this.perPageLimit = options.perPageLimit || 20;
+Documents.AllDocs = PagingCollection.extend({
+  model: Documents.Doc,
+  documentation: function () {
+    return FauxtonAPI.constants.DOC_URLS.GENERAL;
+  },
+  initialize: function (_models, options) {
+    this.viewMeta = options.viewMeta;
+    this.database = options.database;
+    this.params = _.clone(options.params);
 
-      if (!this.params.limit) {
-        this.params.limit = this.perPageLimit;
-      }
-    },
+    this.on("remove", this.decrementTotalRows, this);
+    this.perPageLimit = options.perPageLimit || 20;
 
-    isEditable: function () {
-      return true;
-    },
-
-    urlRef: function (context, params) {
-      var query = "";
-
-      if (params) {
-        if (!_.isEmpty(params)) {
-          query = "?" + $.param(params);
-        } else {
-          query = '';
-        }
-      } else if (this.params) {
-        query = "?" + $.param(this.params);
-      }
-      if (_.isUndefined(context)) {
-        context = 'server';
-      }
-      return FauxtonAPI.urls('allDocs', context, this.database.safeID(), query);
-    },
-
-    url: function () {
-      return this.urlRef.apply(this, arguments);
-    },
-
-    simple: function () {
-      var docs = this.map(function (item) {
-        return {
-          _id: item.id,
-          _rev: item.get('_rev'),
-        };
-      });
-
-      return new Documents.AllDocs(docs, {
-        database: this.database,
-        params: this.params
-      });
-    },
-
-    totalRows: function () {
-      return this.viewMeta.total_rows || "unknown";
-    },
-
-    decrementTotalRows: function () {
-      if (this.viewMeta.total_rows) {
-        this.viewMeta.total_rows = this.viewMeta.total_rows - 1;
-        this.trigger('totalRows:decrement');
-      }
-    },
+    if (!this.params.limit) {
+      this.params.limit = this.perPageLimit;
+    }
+  },
+
+  isEditable: function () {
+    return true;
+  },
+
+  urlRef: function (context, params) {
+    var query = "";
 
-    updateSeq: function () {
-      if (!this.viewMeta) {
-        return false;
+    if (params) {
+      if (!_.isEmpty(params)) {
+        query = "?" + $.param(params);
+      } else {
+        query = '';
       }
-      return this.viewMeta.update_seq || false;
-    },
-
-    parse: function (resp) {
-      var rows = resp.rows;
-
-      // remove any query errors that may return without doc info
-      // important for when querying keys on all docs
-      var cleanRows = _.filter(rows, function (row) {
-        return row.value;
-      });
-
-      resp.rows = _.map(cleanRows, function (row) {
-        var res = {
-          _id: row.id,
-          _rev: row.value.rev,
-          value: row.value,
-          key: row.key
-        };
-
-        if (row.doc) {
-          res.doc = row.doc;
-        }
-
-        return res;
-      });
-
-      return PagingCollection.prototype.parse.call(this, resp);
-    },
-
-    clone: function () {
-      return new this.constructor(this.models, {
-        database: this.database,
-        params: this.params,
-        paging: this.paging
-      });
+    } else if (this.params) {
+      query = "?" + $.param(this.params);
+    }
+    if (_.isUndefined(context)) {
+      context = 'server';
+    }
+    return FauxtonAPI.urls('allDocs', context, this.database.safeID(), query);
+  },
+
+  url: function () {
+    return this.urlRef.apply(this, arguments);
+  },
+
+  simple: function () {
+    var docs = this.map(function (item) {
+      return {
+        _id: item.id,
+        _rev: item.get('_rev'),
+      };
+    });
+
+    return new Documents.AllDocs(docs, {
+      database: this.database,
+      params: this.params
+    });
+  },
+
+  totalRows: function () {
+    return this.viewMeta.total_rows || "unknown";
+  },
+
+  decrementTotalRows: function () {
+    if (this.viewMeta.total_rows) {
+      this.viewMeta.total_rows = this.viewMeta.total_rows - 1;
+      this.trigger('totalRows:decrement');
     }
-  });
+  },
 
-  return Documents;
+  updateSeq: function () {
+    if (!this.viewMeta) {
+      return false;
+    }
+    return this.viewMeta.update_seq || false;
+  },
+
+  parse: function (resp) {
+    var rows = resp.rows;
+
+    // remove any query errors that may return without doc info
+    // important for when querying keys on all docs
+    var cleanRows = _.filter(rows, function (row) {
+      return row.value;
+    });
+
+    resp.rows = _.map(cleanRows, function (row) {
+      var res = {
+        _id: row.id,
+        _rev: row.value.rev,
+        value: row.value,
+        key: row.key
+      };
+
+      if (row.doc) {
+        res.doc = row.doc;
+      }
+
+      return res;
+    });
+
+    return PagingCollection.prototype.parse.call(this, resp);
+  },
+
+  clone: function () {
+    return new this.constructor(this.models, {
+      database: this.database,
+      params: this.params,
+      paging: this.paging
+    });
+  }
 });
+
+export default Documents;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/shared-routes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/shared-routes.js b/app/addons/documents/shared-routes.js
index 9dd7a29..7a2c620 100644
--- a/app/addons/documents/shared-routes.js
+++ b/app/addons/documents/shared-routes.js
@@ -10,142 +10,138 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../app',
-  '../../core/api',
-  './shared-resources',
-  '../databases/base',
-  '../fauxton/components',
-  './pagination/actions',
-  './index-results/stores',
-   './sidebar/sidebar.react',
-   './sidebar/actions'
-], function (app, FauxtonAPI, Documents, Databases, Components, PaginationActions, IndexResultStores,
-  SidebarComponents, SidebarActions) {
-
-
-  // The Documents section is built up a lot of different route object which share code. This contains
-  // base functionality that can be used across routes / addons
-  var BaseRoute = FauxtonAPI.RouteObject.extend({
-    layout: 'with_tabs_sidebar',
-    selectedHeader: 'Databases',
-    overrideBreadcrumbs: true,
-
-    createDesignDocsCollection: function () {
-      this.designDocs = new Documents.AllDocs(null, {
-        database: this.database,
-        paging: {
-          pageSize: 500
-        },
-        params: {
-          startkey: '_design/',
-          endkey: '_design0',
-          include_docs: true,
-          limit: 500
-        }
-      });
-    },
-
-    onSelectDatabase: function (dbName) {
-      this.cleanup();
-      this.initViews(dbName);
-
-      var url = FauxtonAPI.urls('allDocs', 'app',  app.utils.safeURLName(dbName), '');
-      FauxtonAPI.navigate(url, {
-        trigger: true
-      });
-
-      // we need to start listening again because cleanup() removed the listener, but in this case
-      // initialize() doesn't fire to re-set up the listener
-      this.listenToLookaheadTray();
-    },
-
-    listenToLookaheadTray: function () {
-      this.listenTo(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase);
-    },
-
-    getAllDatabases: function () {
-      return new Databases.List();  //getAllDatabases() can be overwritten instead of hard coded into initViews
-    },
-
-    showQueryOptions: function (urlParams, ddoc, viewName) {
-      var promise = this.designDocs.fetch({reset: true}),
-      that = this,
-      hasReduceFunction;
-
-      promise.then(function (resp) {
-        var design = _.findWhere(that.designDocs.models, {id: '_design/' + ddoc});
-        !_.isUndefined(hasReduceFunction = design.attributes.doc.views[viewName].reduce);
-
-        that.rightHeader.showQueryOptions();
-        that.rightHeader.resetQueryOptions({
-          queryParams: urlParams,
-          hasReduce: hasReduceFunction,
-          showReduce: !_.isUndefined(hasReduceFunction),
-          viewName: viewName,
-          ddocName: ddoc
-        });
+import app from "../../app";
+import FauxtonAPI from "../../core/api";
+import Documents from "./shared-resources";
+import Databases from "../databases/base";
+import Components from "../fauxton/components";
+import PaginationActions from "./pagination/actions";
+import IndexResultStores from "./index-results/stores";
+import SidebarComponents from "./sidebar/sidebar.react";
+import SidebarActions from "./sidebar/actions";
+
+
+// The Documents section is built up a lot of different route object which share code. This contains
+// base functionality that can be used across routes / addons
+var BaseRoute = FauxtonAPI.RouteObject.extend({
+  layout: 'with_tabs_sidebar',
+  selectedHeader: 'Databases',
+  overrideBreadcrumbs: true,
+
+  createDesignDocsCollection: function () {
+    this.designDocs = new Documents.AllDocs(null, {
+      database: this.database,
+      paging: {
+        pageSize: 500
+      },
+      params: {
+        startkey: '_design/',
+        endkey: '_design0',
+        include_docs: true,
+        limit: 500
+      }
+    });
+  },
+
+  onSelectDatabase: function (dbName) {
+    this.cleanup();
+    this.initViews(dbName);
+
+    var url = FauxtonAPI.urls('allDocs', 'app',  app.utils.safeURLName(dbName), '');
+    FauxtonAPI.navigate(url, {
+      trigger: true
+    });
+
+    // we need to start listening again because cleanup() removed the listener, but in this case
+    // initialize() doesn't fire to re-set up the listener
+    this.listenToLookaheadTray();
+  },
+
+  listenToLookaheadTray: function () {
+    this.listenTo(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase);
+  },
+
+  getAllDatabases: function () {
+    return new Databases.List();  //getAllDatabases() can be overwritten instead of hard coded into initViews
+  },
+
+  showQueryOptions: function (urlParams, ddoc, viewName) {
+    var promise = this.designDocs.fetch({reset: true}),
+    that = this,
+    hasReduceFunction;
+
+    promise.then(function (resp) {
+      var design = _.findWhere(that.designDocs.models, {id: '_design/' + ddoc});
+      !_.isUndefined(hasReduceFunction = design.attributes.doc.views[viewName].reduce);
+
+      that.rightHeader.showQueryOptions();
+      that.rightHeader.resetQueryOptions({
+        queryParams: urlParams,
+        hasReduce: hasReduceFunction,
+        showReduce: !_.isUndefined(hasReduceFunction),
+        viewName: viewName,
+        ddocName: ddoc
       });
-    },
-
-    addLeftHeader: function () {
-      this.leftheader = this.setView('#breadcrumbs', new Components.LeftHeader({
-        databaseName: this.database.safeID(),
-        crumbs: this.getCrumbs(this.database),
-        lookaheadTrayOptions: {
-          databaseCollection: this.allDatabases,
-          toggleEventName: 'lookaheadTray:toggle',
-          onUpdateEventName: 'lookaheadTray:update',
-          placeholder: 'Enter database name'
-        }
-      }));
-    },
-
-    addSidebar: function (selectedNavItem) {
-      var options = {
-        designDocs: this.designDocs,
-        database: this.database
-      };
-      if (selectedNavItem) {
-        options.selectedNavItem = selectedNavItem;
+    });
+  },
+
+  addLeftHeader: function () {
+    this.leftheader = this.setView('#breadcrumbs', new Components.LeftHeader({
+      databaseName: this.database.safeID(),
+      crumbs: this.getCrumbs(this.database),
+      lookaheadTrayOptions: {
+        databaseCollection: this.allDatabases,
+        toggleEventName: 'lookaheadTray:toggle',
+        onUpdateEventName: 'lookaheadTray:update',
+        placeholder: 'Enter database name'
       }
-
-      SidebarActions.newOptions(options);
-      this.setComponent("#sidebar-content", SidebarComponents.SidebarController);
-    },
-
-    getCrumbs: function (database) {
-      var name = _.isObject(database) ? database.id : database,
-        dbname = app.utils.safeURLName(name);
-
-      return [
-        { "type": "back", "link": FauxtonAPI.urls('allDBs', 'app')},
-        { "name": database.id, "link": FauxtonAPI.urls('allDocs', 'app', dbname, '?limit=' + Databases.DocLimit), className: "lookahead-tray-link" }
-      ];
-    },
-
-    ddocInfo: function (designDoc, designDocs, view) {
-      return {
-        id: "_design/" + designDoc,
-        currView: view,
-        designDocs: designDocs
-      };
-    },
-
-    createParams: function (options) {
-      var urlParams = app.getParams(options),
-          params = Documents.QueryParams.parse(urlParams);
-
-      PaginationActions.setDocumentLimit(parseInt(urlParams.limit, 10));
-
-      var limit = IndexResultStores.indexResultsStore.getPerPage();
-      return {
-        urlParams: urlParams,
-        docParams: _.extend(params, {limit: limit})
-      };
+    }));
+  },
+
+  addSidebar: function (selectedNavItem) {
+    var options = {
+      designDocs: this.designDocs,
+      database: this.database
+    };
+    if (selectedNavItem) {
+      options.selectedNavItem = selectedNavItem;
     }
-  });
-
 
-  return BaseRoute;
+    SidebarActions.newOptions(options);
+    this.setComponent("#sidebar-content", SidebarComponents.SidebarController);
+  },
+
+  getCrumbs: function (database) {
+    var name = _.isObject(database) ? database.id : database,
+      dbname = app.utils.safeURLName(name);
+
+    return [
+      { "type": "back", "link": FauxtonAPI.urls('allDBs', 'app')},
+      { "name": database.id, "link": FauxtonAPI.urls('allDocs', 'app', dbname, '?limit=' + Databases.DocLimit), className: "lookahead-tray-link" }
+    ];
+  },
+
+  ddocInfo: function (designDoc, designDocs, view) {
+    return {
+      id: "_design/" + designDoc,
+      currView: view,
+      designDocs: designDocs
+    };
+  },
+
+  createParams: function (options) {
+    var urlParams = app.getParams(options),
+        params = Documents.QueryParams.parse(urlParams);
+
+    PaginationActions.setDocumentLimit(parseInt(urlParams.limit, 10));
+
+    var limit = IndexResultStores.indexResultsStore.getPerPage();
+    return {
+      urlParams: urlParams,
+      docParams: _.extend(params, {limit: limit})
+    };
+  }
 });
+
+
+export default BaseRoute;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/sidebar/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/actions.js b/app/addons/documents/sidebar/actions.js
index 92782a8..1dc238e 100644
--- a/app/addons/documents/sidebar/actions.js
+++ b/app/addons/documents/sidebar/actions.js
@@ -10,149 +10,144 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../../app',
-  '../../../core/api',
-  './actiontypes',
-  './stores.react'
-],
-function (app, FauxtonAPI, ActionTypes, Stores) {
-  var store = Stores.sidebarStore;
-
-  function newOptions (options) {
-    if (options.database.safeID() !== store.getDatabaseName()) {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.SIDEBAR_FETCHING
-      });
-    }
-
-    options.designDocs.fetch().then(function () {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.SIDEBAR_NEW_OPTIONS,
-        options: options
-      });
-    });
-  }
-
-  function updateDesignDocs (designDocs) {
-    designDocs.fetch().then(function () {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS,
-        options: {
-          designDocs: designDocs
-        }
-      });
-    });
-  }
-
-  function toggleContent (designDoc, indexGroup) {
-    FauxtonAPI.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) {
-    var settings = $.extend(true, {}, {
-      designDocName: '',
-      designDocSection: '',
-      indexName: ''
-    }, params);
-    settings.navItem = navItem;
-
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SIDEBAR_SET_SELECTED_NAV_ITEM,
-      options: settings
-    });
-  }
-
-  function refresh () {
-    FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_REFRESH });
-  }
-
-  function showDeleteIndexModal (indexName, designDocName, indexLabel, onDelete) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SIDEBAR_SHOW_DELETE_INDEX_MODAL,
-      options: {
-        indexName: indexName,
-        indexLabel: indexLabel,
-        designDocName: designDocName,
-        onDelete: onDelete
-      }
-    });
-  }
-
-  function hideDeleteIndexModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL });
-  }
-
-  function showCloneIndexModal (indexName, designDocName, indexLabel, onSubmit) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SIDEBAR_SHOW_CLONE_INDEX_MODAL,
-      options: {
-        sourceIndexName: indexName,
-        sourceDesignDocName: designDocName,
-        onSubmit: onSubmit,
-        indexLabel: indexLabel,
-        cloneIndexModalTitle: 'Clone ' + indexLabel
-      }
-    });
-  }
-
-  function hideCloneIndexModal () {
-    FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL });
-  }
-
-  function updateNewDesignDocName (designDocName) {
+import app from "../../../app";
+import FauxtonAPI from "../../../core/api";
+import ActionTypes from "./actiontypes";
+import Stores from "./stores.react";
+var store = Stores.sidebarStore;
+
+function newOptions (options) {
+  if (options.database.safeID() !== store.getDatabaseName()) {
     FauxtonAPI.dispatch({
-      type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED,
-      options: {
-        value: designDocName
-      }
+      type: ActionTypes.SIDEBAR_FETCHING
     });
   }
 
-  function selectDesignDoc (designDoc) {
+  options.designDocs.fetch().then(function () {
     FauxtonAPI.dispatch({
-      type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE,
-      options: {
-        value: designDoc
-      }
+      type: ActionTypes.SIDEBAR_NEW_OPTIONS,
+      options: options
     });
-  }
+  });
+}
 
-  function setNewCloneIndexName (indexName) {
+function updateDesignDocs (designDocs) {
+  designDocs.fetch().then(function () {
     FauxtonAPI.dispatch({
-      type: ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME,
+      type: ActionTypes.SIDEBAR_UPDATED_DESIGN_DOCS,
       options: {
-        value: indexName
+        designDocs: designDocs
       }
     });
-  }
-
-
-  return {
-    newOptions: newOptions,
-    updateDesignDocs: updateDesignDocs,
-    toggleContent: toggleContent,
-    selectNavItem: selectNavItem,
-    refresh: refresh,
-    showDeleteIndexModal: showDeleteIndexModal,
-    hideDeleteIndexModal: hideDeleteIndexModal,
-    showCloneIndexModal: showCloneIndexModal,
-    hideCloneIndexModal: hideCloneIndexModal,
-    updateNewDesignDocName: updateNewDesignDocName,
-    selectDesignDoc: selectDesignDoc,
-    setNewCloneIndexName: setNewCloneIndexName
-  };
-
-});
+  });
+}
+
+function toggleContent (designDoc, indexGroup) {
+  FauxtonAPI.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) {
+  var settings = $.extend(true, {}, {
+    designDocName: '',
+    designDocSection: '',
+    indexName: ''
+  }, params);
+  settings.navItem = navItem;
+
+  FauxtonAPI.dispatch({
+    type: ActionTypes.SIDEBAR_SET_SELECTED_NAV_ITEM,
+    options: settings
+  });
+}
+
+function refresh () {
+  FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_REFRESH });
+}
+
+function showDeleteIndexModal (indexName, designDocName, indexLabel, onDelete) {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.SIDEBAR_SHOW_DELETE_INDEX_MODAL,
+    options: {
+      indexName: indexName,
+      indexLabel: indexLabel,
+      designDocName: designDocName,
+      onDelete: onDelete
+    }
+  });
+}
+
+function hideDeleteIndexModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_HIDE_DELETE_INDEX_MODAL });
+}
+
+function showCloneIndexModal (indexName, designDocName, indexLabel, onSubmit) {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.SIDEBAR_SHOW_CLONE_INDEX_MODAL,
+    options: {
+      sourceIndexName: indexName,
+      sourceDesignDocName: designDocName,
+      onSubmit: onSubmit,
+      indexLabel: indexLabel,
+      cloneIndexModalTitle: 'Clone ' + indexLabel
+    }
+  });
+}
+
+function hideCloneIndexModal () {
+  FauxtonAPI.dispatch({ type: ActionTypes.SIDEBAR_HIDE_CLONE_INDEX_MODAL });
+}
+
+function updateNewDesignDocName (designDocName) {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED,
+    options: {
+      value: designDocName
+    }
+  });
+}
+
+function selectDesignDoc (designDoc) {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE,
+    options: {
+      value: designDoc
+    }
+  });
+}
+
+function setNewCloneIndexName (indexName) {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME,
+    options: {
+      value: indexName
+    }
+  });
+}
+
+
+export default {
+  newOptions: newOptions,
+  updateDesignDocs: updateDesignDocs,
+  toggleContent: toggleContent,
+  selectNavItem: selectNavItem,
+  refresh: refresh,
+  showDeleteIndexModal: showDeleteIndexModal,
+  hideDeleteIndexModal: hideDeleteIndexModal,
+  showCloneIndexModal: showCloneIndexModal,
+  hideCloneIndexModal: hideCloneIndexModal,
+  updateNewDesignDocName: updateNewDesignDocName,
+  selectDesignDoc: selectDesignDoc,
+  setNewCloneIndexName: setNewCloneIndexName
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/sidebar/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/actiontypes.js b/app/addons/documents/sidebar/actiontypes.js
index b05e3e1..6a1ebd4 100644
--- a/app/addons/documents/sidebar/actiontypes.js
+++ b/app/addons/documents/sidebar/actiontypes.js
@@ -10,20 +10,18 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([], function () {
-  return {
-    SIDEBAR_SET_SELECTED_NAV_ITEM: 'SIDEBAR_SET_SELECTED_NAV_ITEM',
-    SIDEBAR_NEW_OPTIONS: 'SIDEBAR_NEW_OPTIONS',
-    SIDEBAR_TOGGLE_CONTENT: 'SIDEBAR_TOGGLE_CONTENT',
-    SIDEBAR_FETCHING: 'SIDEBAR_FETCHING',
-    SIDEBAR_REFRESH: 'SIDEBAR_REFRESH',
-    SIDEBAR_SHOW_DELETE_INDEX_MODAL: 'SIDEBAR_SHOW_DELETE_INDEX_MODAL',
-    SIDEBAR_HIDE_DELETE_INDEX_MODAL: 'SIDEBAR_HIDE_DELETE_INDEX_MODAL',
-    SIDEBAR_SHOW_CLONE_INDEX_MODAL: 'SIDEBAR_SHOW_CLONE_INDEX_MODAL',
-    SIDEBAR_HIDE_CLONE_INDEX_MODAL: 'SIDEBAR_HIDE_CLONE_INDEX_MODAL',
-    SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE: 'SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE',
-    SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED: 'SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED',
-    SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME: 'SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME',
-    SIDEBAR_UPDATED_DESIGN_DOCS: 'SIDEBAR_UPDATED_DESIGN_DOCS'
-  };
-});
+export default {
+  SIDEBAR_SET_SELECTED_NAV_ITEM: 'SIDEBAR_SET_SELECTED_NAV_ITEM',
+  SIDEBAR_NEW_OPTIONS: 'SIDEBAR_NEW_OPTIONS',
+  SIDEBAR_TOGGLE_CONTENT: 'SIDEBAR_TOGGLE_CONTENT',
+  SIDEBAR_FETCHING: 'SIDEBAR_FETCHING',
+  SIDEBAR_REFRESH: 'SIDEBAR_REFRESH',
+  SIDEBAR_SHOW_DELETE_INDEX_MODAL: 'SIDEBAR_SHOW_DELETE_INDEX_MODAL',
+  SIDEBAR_HIDE_DELETE_INDEX_MODAL: 'SIDEBAR_HIDE_DELETE_INDEX_MODAL',
+  SIDEBAR_SHOW_CLONE_INDEX_MODAL: 'SIDEBAR_SHOW_CLONE_INDEX_MODAL',
+  SIDEBAR_HIDE_CLONE_INDEX_MODAL: 'SIDEBAR_HIDE_CLONE_INDEX_MODAL',
+  SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE: 'SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE',
+  SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED: 'SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED',
+  SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME: 'SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME',
+  SIDEBAR_UPDATED_DESIGN_DOCS: 'SIDEBAR_UPDATED_DESIGN_DOCS'
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/sidebar/sidebar.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/sidebar/sidebar.react.jsx b/app/addons/documents/sidebar/sidebar.react.jsx
index ce38840..66f21c0 100644
--- a/app/addons/documents/sidebar/sidebar.react.jsx
+++ b/app/addons/documents/sidebar/sidebar.react.jsx
@@ -10,662 +10,652 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-   '../../../app',
-  '../../../core/api',
-  'react',
-  'react-dom',
-  './stores.react',
-  './actions',
-  '../../components/react-components.react',
-  '../../components/stores',
-  '../../components/actions',
-  '../index-editor/actions',
-  '../index-editor/components.react',
-  '../../fauxton/components.react',
-  '../../documents/views',
-  '../../documents/helpers',
-  'react-bootstrap',
-  '../../../../assets/js/plugins/prettify'
-],
-
-function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, Components, ComponentsStore, ComponentsActions,
-  IndexEditorActions, IndexEditorComponents, GeneralComponents, DocumentViews, DocumentHelper, ReactBootstrap) {
-
-  var DeleteDBModal = DocumentViews.Views.DeleteDBModal;
-
-  var store = Stores.sidebarStore;
-  var LoadLines = Components.LoadLines;
-  var DesignDocSelector = IndexEditorComponents.DesignDocSelector;
-  var OverlayTrigger = ReactBootstrap.OverlayTrigger;
-  var Popover = ReactBootstrap.Popover;
-  var Modal = ReactBootstrap.Modal;
-  var ConfirmationModal = GeneralComponents.ConfirmationModal;
-
-  var DeleteDatabaseModal = Components.DeleteDatabaseModal;
-  var deleteDbModalStore = ComponentsStore.deleteDbModalStore;
-
-
-  var MainSidebar = React.createClass({
-    propTypes: {
-      selectedNavItem: React.PropTypes.string.isRequired
-    },
-
-    getNewButtonLinks: function () {  // these are links for the sidebar '+' on All Docs and All Design Docs
-      return DocumentHelper.getNewButtonLinks(this.props.databaseName);
-    },
-
-    buildDocLinks: function () {
-      var base = FauxtonAPI.urls('base', 'app', this.props.databaseName);
-      return FauxtonAPI.getExtensions('docLinks').map(function (link) {
-        return (
-          <li key={link.url} className={this.getNavItemClass(link.url)}>
-            <a id={link.url} href={base + link.url}>{link.title}</a>
-          </li>
-        );
-      }, this);
-    },
-
-    getNavItemClass: function (navItem) {
-      return (navItem === this.props.selectedNavItem) ? 'active' : '';
-    },
-
-    render: function () {
-      var docLinks = this.buildDocLinks();
-      var changesUrl     = '#' + FauxtonAPI.urls('changes', 'app', this.props.databaseName, '');
-      var permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', this.props.databaseName);
-      var databaseUrl    = FauxtonAPI.urls('allDocs', 'app', this.props.databaseName, '');
-      var mangoQueryUrl  = FauxtonAPI.urls('mango', 'query-app', this.props.databaseName);
-      var runQueryWithMangoText = app.i18n.en_US['run-query-with-mango'];
-      var buttonLinks = this.getNewButtonLinks();
-
+import app from "../../../app";
+import FauxtonAPI from "../../../core/api";
+import React from "react";
+import ReactDOM from "react-dom";
+import Stores from "./stores.react";
+import Actions from "./actions";
+import Components from "../../components/react-components.react";
+import ComponentsStore from "../../components/stores";
+import ComponentsActions from "../../components/actions";
+import IndexEditorActions from "../index-editor/actions";
+import IndexEditorComponents from "../index-editor/components.react";
+import GeneralComponents from "../../fauxton/components.react";
+import DocumentViews from "../../documents/views";
+import DocumentHelper from "../../documents/helpers";
+import { OverlayTrigger, Popover, Modal } from "react-bootstrap";
+import "../../../../assets/js/plugins/prettify";
+
+var DeleteDBModal = DocumentViews.Views.DeleteDBModal;
+
+var store = Stores.sidebarStore;
+var LoadLines = Components.LoadLines;
+var DesignDocSelector = IndexEditorComponents.DesignDocSelector;
+var ConfirmationModal = GeneralComponents.ConfirmationModal;
+
+var DeleteDatabaseModal = Components.DeleteDatabaseModal;
+var deleteDbModalStore = ComponentsStore.deleteDbModalStore;
+
+
+var MainSidebar = React.createClass({
+  propTypes: {
+    selectedNavItem: React.PropTypes.string.isRequired
+  },
+
+  getNewButtonLinks: function () {  // these are links for the sidebar '+' on All Docs and All Design Docs
+    return DocumentHelper.getNewButtonLinks(this.props.databaseName);
+  },
+
+  buildDocLinks: function () {
+    var base = FauxtonAPI.urls('base', 'app', this.props.databaseName);
+    return FauxtonAPI.getExtensions('docLinks').map(function (link) {
       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">
-              <Components.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">
-              <Components.MenuDropDown links={buttonLinks} />
-            </div>
-          </li>
-        </ul>
+        <li key={link.url} className={this.getNavItemClass(link.url)}>
+          <a id={link.url} href={base + link.url}>{link.title}</a>
+        </li>
       );
-    }
-  });
-
-
-  var IndexSection = React.createClass({
-
-    propTypes: {
-      urlNamespace: React.PropTypes.string.isRequired,
-      indexLabel: React.PropTypes.string.isRequired,
-      database: React.PropTypes.object.isRequired,
-      designDocName: React.PropTypes.string.isRequired,
-      items: React.PropTypes.array.isRequired,
-      isExpanded: React.PropTypes.bool.isRequired,
-      selectedIndex: React.PropTypes.string.isRequired,
-      onDelete: React.PropTypes.func.isRequired,
-      onClone: React.PropTypes.func.isRequired
-    },
-
-    getInitialState: function () {
-      return {
-        placement: 'bottom'
-      };
-    },
-
-    // this dynamically changes the placement of the menu (top/bottom) to prevent it going offscreen and causing some
-    // unsightly shifting
-    setPlacement: function (rowId) {
-      var rowTop = document.getElementById(rowId).getBoundingClientRect().top;
-      var toggleHeight = 150; // the height of the menu overlay, arrow, view row
-      var placement = (rowTop + toggleHeight > window.innerHeight) ? 'top' : 'bottom';
-      this.setState({ placement: placement });
-    },
-
-    createItems: function () {
-
-      // sort the indexes alphabetically
-      var sortedItems = this.props.items.sort();
-
-      return _.map(sortedItems, function (indexName, index) {
-        var href = FauxtonAPI.urls(this.props.urlNamespace, 'app', this.props.database.id, this.props.designDocName);
-        var className = (this.props.selectedIndex === indexName) ? 'active' : '';
-
-        return (
-          <li className={className} key={index}>
-            <a
-              id={this.props.designDocName + '_' + indexName}
-              href={"#/" + href + indexName}
-              className="toggle-view">
-              {indexName}
-            </a>
-            <OverlayTrigger
-              ref={"indexMenu-" + index}
-              trigger="click"
-              onEnter={this.setPlacement.bind(this, this.props.designDocName + '_' + indexName)}
-              placement={this.state.placement}
-              rootClose={true}
-              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>
-        );
-      }, this);
-    },
-
-    indexAction: function (action, params, e) {
-      e.preventDefault();
-
-      // ensures the menu gets closed. The hide() on the ref doesn't consistently close it
-      $('body').trigger('click');
-
-      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: function (e) {
-      e.preventDefault();
-      var newToggleState = !this.props.isExpanded;
-      var state = newToggleState ? 'show' : 'hide';
-      $(ReactDOM.findDOMNode(this)).find('.accordion-body').collapse(state);
-      this.props.toggle(this.props.designDocName, this.props.title);
-    },
-
-    render: function () {
-
-      // 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;
-      }
+    }, this);
+  },
+
+  getNavItemClass: function (navItem) {
+    return (navItem === this.props.selectedNavItem) ? 'active' : '';
+  },
+
+  render: function () {
+    var docLinks = this.buildDocLinks();
+    var changesUrl     = '#' + FauxtonAPI.urls('changes', 'app', this.props.databaseName, '');
+    var permissionsUrl = '#' + FauxtonAPI.urls('permissions', 'app', this.props.databaseName);
+    var databaseUrl    = FauxtonAPI.urls('allDocs', 'app', this.props.databaseName, '');
+    var mangoQueryUrl  = FauxtonAPI.urls('mango', 'query-app', this.props.databaseName);
+    var runQueryWithMangoText = app.i18n.en_US['run-query-with-mango'];
+    var 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">
+            <Components.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">
+            <Components.MenuDropDown links={buttonLinks} />
+          </div>
+        </li>
+      </ul>
+    );
+  }
+});
 
-      var toggleClassNames = 'accordion-header index-group-header';
-      var toggleBodyClassNames = 'index-list accordion-body collapse';
-      if (this.props.isExpanded) {
-        toggleClassNames += ' down';
-        toggleBodyClassNames += ' in';
-      }
 
-      var title = this.props.title;
-      var designDocName = this.props.designDocName;
-      var linkId = "nav-design-function-" + designDocName + this.props.selector;
+var IndexSection = React.createClass({
+
+  propTypes: {
+    urlNamespace: React.PropTypes.string.isRequired,
+    indexLabel: React.PropTypes.string.isRequired,
+    database: React.PropTypes.object.isRequired,
+    designDocName: React.PropTypes.string.isRequired,
+    items: React.PropTypes.array.isRequired,
+    isExpanded: React.PropTypes.bool.isRequired,
+    selectedIndex: React.PropTypes.string.isRequired,
+    onDelete: React.PropTypes.func.isRequired,
+    onClone: React.PropTypes.func.isRequired
+  },
+
+  getInitialState: function () {
+    return {
+      placement: 'bottom'
+    };
+  },
+
+  // this dynamically changes the placement of the menu (top/bottom) to prevent it going offscreen and causing some
+  // unsightly shifting
+  setPlacement: function (rowId) {
+    var rowTop = document.getElementById(rowId).getBoundingClientRect().top;
+    var toggleHeight = 150; // the height of the menu overlay, arrow, view row
+    var placement = (rowTop + toggleHeight > window.innerHeight) ? 'top' : 'bottom';
+    this.setState({ placement: placement });
+  },
+
+  createItems: function () {
+
+    // sort the indexes alphabetically
+    var sortedItems = this.props.items.sort();
+
+    return _.map(sortedItems, function (indexName, index) {
+      var href = FauxtonAPI.urls(this.props.urlNamespace, 'app', this.props.database.id, this.props.designDocName);
+      var className = (this.props.selectedIndex === indexName) ? 'active' : '';
 
       return (
-        <li id={linkId}>
-          <a className={toggleClassNames} data-toggle="collapse" onClick={this.toggle}>
-            <div className="fonticon-play"></div>
-            {title}
+        <li className={className} key={index}>
+          <a
+            id={this.props.designDocName + '_' + indexName}
+            href={"#/" + href + indexName}
+            className="toggle-view">
+            {indexName}
           </a>
-          <ul className={toggleBodyClassNames}>
-            {this.createItems()}
-          </ul>
+          <OverlayTrigger
+            ref={"indexMenu-" + index}
+            trigger="click"
+            onEnter={this.setPlacement.bind(this, this.props.designDocName + '_' + indexName)}
+            placement={this.state.placement}
+            rootClose={true}
+            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>
       );
+    }, this);
+  },
+
+  indexAction: function (action, params, e) {
+    e.preventDefault();
+
+    // ensures the menu gets closed. The hide() on the ref doesn't consistently close it
+    $('body').trigger('click');
+
+    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: function (e) {
+    e.preventDefault();
+    var newToggleState = !this.props.isExpanded;
+    var state = newToggleState ? 'show' : 'hide';
+    $(ReactDOM.findDOMNode(this)).find('.accordion-body').collapse(state);
+    this.props.toggle(this.props.designDocName, this.props.title);
+  },
+
+  render: function () {
+
+    // 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;
     }
-  });
-
-
-  var DesignDoc = React.createClass({
-    propTypes: {
-      database: React.PropTypes.object.isRequired,
-      sidebarListTypes: React.PropTypes.array.isRequired,
-      isExpanded: React.PropTypes.bool.isRequired,
-      selectedNavInfo: React.PropTypes.object.isRequired,
-      toggledSections: React.PropTypes.object.isRequired
-    },
-
-    getInitialState: function () {
-      return {
-        updatedSidebarListTypes: this.props.sidebarListTypes
-      };
-    },
-
-    componentWillMount: function () {
-      if (_.isEmpty(this.state.updatedSidebarListTypes) ||
-        (_.has(this.state.updatedSidebarListTypes[0], 'selector') && this.state.updatedSidebarListTypes[0].selector !== 'views')) {
-
-        var 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: function () {
-      return _.map(this.state.updatedSidebarListTypes, function (index, key) {
-        var expanded = _.has(this.props.toggledSections, index.name) && this.props.toggledSections[index.name];
-
-        // if an index in this list is selected, pass that down
-        var 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])} />
-        );
-      }.bind(this));
-    },
-
-    toggle: function (e) {
-      e.preventDefault();
-      var newToggleState = !this.props.isExpanded;
-      var state = newToggleState ? 'show' : 'hide';
-      $(ReactDOM.findDOMNode(this)).find('#' + this.props.designDocName).collapse(state);
-      this.props.toggle(this.props.designDocName);
-    },
-
-    getNewButtonLinks: function () {
-      var newUrlPrefix = FauxtonAPI.urls('databaseBaseURL', 'app', this.props.database.id);
-      var designDocName = this.props.designDocName;
-
-      var addNewLinks = _.reduce(FauxtonAPI.getExtensions('sidebar:links'), function (menuLinks, link) {
-        menuLinks.push({
-          title: link.title,
-          url: '#' + newUrlPrefix + '/' + link.url + '/' + designDocName,
-          icon: 'fonticon-plus-circled'
-        });
-        return menuLinks;
-      }, [{
-        title: 'New View',
-        url: '#' + FauxtonAPI.urls('new', 'addView', this.props.database.id, designDocName),
-        icon: 'fonticon-plus-circled'
-      }]);
-
-      return [{
-        title: 'Add New',
-        links: addNewLinks
-      }];
-    },
-
-    render: function () {
-      var buttonLinks = this.getNewButtonLinks();
-      var toggleClassNames = 'design-doc-section accordion-header';
-      var toggleBodyClassNames = 'design-doc-body accordion-body collapse';
-
-      if (this.props.isExpanded) {
-        toggleClassNames += ' down';
-        toggleBodyClassNames += ' in';
-      }
-      var designDocName = this.props.designDocName;
-      var designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', this.props.database.id, designDocName);
-      var 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'>
-              <Components.MenuDropDown links={buttonLinks} />
-            </div>
-          </div>
-          <ul className={toggleBodyClassNames} id={this.props.designDocName}>
-            <li className={metadataRowClass}>
-              <a href={"#/" + designDocMetaUrl} className="toggle-view accordion-header">
-                Metadata
-              </a>
-            </li>
-            {this.indexList()}
-          </ul>
-        </li>
-      );
+    var toggleClassNames = 'accordion-header index-group-header';
+    var toggleBodyClassNames = 'index-list accordion-body collapse';
+    if (this.props.isExpanded) {
+      toggleClassNames += ' down';
+      toggleBodyClassNames += ' in';
     }
-  });
-
-
-  var DesignDocList = React.createClass({
-    componentWillMount: function () {
-      var list = FauxtonAPI.getExtensions('sidebar:list');
-      this.sidebarListTypes = _.isUndefined(list) ? [] : list;
-    },
-
-    designDocList: function () {
-      return _.map(this.props.designDocs, function (designDoc, key) {
-        var ddName = designDoc.safeId;
-
-        // only pass down the selected nav info and toggle info if they're relevant for this particular design doc
-        var expanded = false,
-          toggledSections = {};
-        if (_.has(this.props.toggledSections, ddName)) {
-          expanded = this.props.toggledSections[ddName].visible;
-          toggledSections = this.props.toggledSections[ddName].indexGroups;
-        }
-
-        var 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} />
-        );
-      }.bind(this));
-    },
-
-    render: function () {
-      return (
-        <ul className="nav nav-list">
-          {this.designDocList()}
+
+    var title = this.props.title;
+    var designDocName = this.props.designDocName;
+    var 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>
+        <ul className={toggleBodyClassNames}>
+          {this.createItems()}
         </ul>
-      );
-    }
-  });
-
-  var SidebarController = React.createClass({
-    getStoreState: function () {
-      return {
-        database: store.getDatabase(),
-        selectedNav: store.getSelected(),
-        designDocs: store.getDesignDocs(),
-        designDocList: store.getDesignDocList(),
-        availableDesignDocIds: store.getAvailableDesignDocs(),
-        toggledSections: store.getToggledSections(),
-        isLoading: store.isLoading(),
-        database: store.getDatabase(),
-        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()
-      };
-    },
-
-    getInitialState: function () {
-      return this.getStoreState();
-    },
-
-    componentDidMount: function () {
-      store.on('change', this.onChange, this);
-      deleteDbModalStore.on('change', this.onChange, this);
-    },
-
-    componentWillUnmount: function () {
-      store.off('change', this.onChange);
-      deleteDbModalStore.off('change', this.onChange, this);
-    },
-
-    onChange: function () {
-      if (this.isMounted()) {
-        this.setState(this.getStoreState());
-      }
-    },
-
-    showDeleteDatabaseModal: function (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: function () {
-
-      // 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
-      var 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: function () {
-      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
+      </li>
+    );
+  }
+});
+
+
+var DesignDoc = React.createClass({
+  propTypes: {
+    database: React.PropTypes.object.isRequired,
+    sidebarListTypes: React.PropTypes.array.isRequired,
+    isExpanded: React.PropTypes.bool.isRequired,
+    selectedNavInfo: React.PropTypes.object.isRequired,
+    toggledSections: React.PropTypes.object.isRequired
+  },
+
+  getInitialState: function () {
+    return {
+      updatedSidebarListTypes: this.props.sidebarListTypes
+    };
+  },
+
+  componentWillMount: function () {
+    if (_.isEmpty(this.state.updatedSidebarListTypes) ||
+      (_.has(this.state.updatedSidebarListTypes[0], 'selector') && this.state.updatedSidebarListTypes[0].selector !== 'views')) {
+
+      var 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: function () {
+    return _.map(this.state.updatedSidebarListTypes, function (index, key) {
+      var expanded = _.has(this.props.toggledSections, index.name) && this.props.toggledSections[index.name];
 
-    render: function () {
-      if (this.state.isLoading) {
-        return <LoadLines />;
+      // if an index in this list is selected, pass that down
+      var selectedIndex = '';
+      if (this.props.selectedNavInfo.designDocSection === index.name) {
+        selectedIndex = this.props.selectedNavInfo.indexName;
       }
 
       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>
+        <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])} />
       );
+    }.bind(this));
+  },
+
+  toggle: function (e) {
+    e.preventDefault();
+    var newToggleState = !this.props.isExpanded;
+    var state = newToggleState ? 'show' : 'hide';
+    $(ReactDOM.findDOMNode(this)).find('#' + this.props.designDocName).collapse(state);
+    this.props.toggle(this.props.designDocName);
+  },
+
+  getNewButtonLinks: function () {
+    var newUrlPrefix = FauxtonAPI.urls('databaseBaseURL', 'app', this.props.database.id);
+    var designDocName = this.props.designDocName;
+
+    var addNewLinks = _.reduce(FauxtonAPI.getExtensions('sidebar:links'), function (menuLinks, link) {
+      menuLinks.push({
+        title: link.title,
+        url: '#' + newUrlPrefix + '/' + link.url + '/' + designDocName,
+        icon: 'fonticon-plus-circled'
+      });
+      return menuLinks;
+    }, [{
+      title: 'New View',
+      url: '#' + FauxtonAPI.urls('new', 'addView', this.props.database.id, designDocName),
+      icon: 'fonticon-plus-circled'
+    }]);
+
+    return [{
+      title: 'Add New',
+      links: addNewLinks
+    }];
+  },
+
+  render: function () {
+    var buttonLinks = this.getNewButtonLinks();
+    var toggleClassNames = 'design-doc-section accordion-header';
+    var toggleBodyClassNames = 'design-doc-body accordion-body collapse';
+
+    if (this.props.isExpanded) {
+      toggleClassNames += ' down';
+      toggleBodyClassNames += ' in';
     }
-  });
-
-
-  var CloneIndexModal = React.createClass({
-    propTypes: {
-      visible: React.PropTypes.bool.isRequired,
-      title: React.PropTypes.string,
-      close: React.PropTypes.func.isRequired,
-      submit: React.PropTypes.func.isRequired,
-      designDocArray: React.PropTypes.array.isRequired,
-      selectedDesignDoc: React.PropTypes.string.isRequired,
-      newDesignDocName: React.PropTypes.string.isRequired,
-      newIndexName: React.PropTypes.string.isRequired,
-      indexLabel: React.PropTypes.string.isRequired
-    },
-
-    getDefaultProps: function () {
-      return {
-        title: 'Clone Index',
-        visible: false
-      };
-    },
-
-    submit: function () {
-      if (!this.refs.designDocSelector.validate()) {
-        return;
-      }
-      if (this.props.newIndexName === '') {
-        FauxtonAPI.addNotification({
-          msg: 'Please enter the new index name.',
-          type: 'error',
-          clear: true
-        });
-        return;
-      }
-      this.props.submit();
-    },
+    var designDocName = this.props.designDocName;
+    var designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', this.props.database.id, designDocName);
+    var 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'>
+            <Components.MenuDropDown links={buttonLinks} />
+          </div>
+        </div>
+        <ul className={toggleBodyClassNames} id={this.props.designDocName}>
+          <li className={metadataRowClass}>
+            <a href={"#/" + designDocMetaUrl} className="toggle-view accordion-header">
+              Metadata
+            </a>
+          </li>
+          {this.indexList()}
+        </ul>
+      </li>
+    );
+  }
+});
+
+
+var DesignDocList = React.createClass({
+  componentWillMount: function () {
+    var list = FauxtonAPI.getExtensions('sidebar:list');
+    this.sidebarListTypes = _.isUndefined(list) ? [] : list;
+  },
+
+  designDocList: function () {
+    return _.map(this.props.designDocs, function (designDoc, key) {
+      var ddName = designDoc.safeId;
 
-    close: function (e) {
-      if (e) {
-        e.preventDefault();
+      // only pass down the selected nav info and toggle info if they're relevant for this particular design doc
+      var expanded = false,
+        toggledSections = {};
+      if (_.has(this.props.toggledSections, ddName)) {
+        expanded = this.props.toggledSections[ddName].visible;
+        toggledSections = this.props.toggledSections[ddName].indexGroups;
       }
-      this.props.close();
-    },
 
-    setNewIndexName: function (e) {
-      Actions.setNewCloneIndexName(e.target.value);
-    },
+      var selectedNavInfo = {};
+      if (this.props.selectedNav.navItem === 'designDoc' && this.props.selectedNav.designDocName === ddName) {
+        selectedNavInfo = this.props.selectedNav;
+      }
 
-    render: function () {
       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="designDocSelector"
-                  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="Enter 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-success save">
-              <i className="icon fonticon-ok-circled" /> Clone {this.props.indexLabel}</button>
-          </Modal.Footer>
-        </Modal>
+        <DesignDoc
+          toggle={this.props.toggle}
+          sidebarListTypes={this.sidebarListTypes}
+          isExpanded={expanded}
+          toggledSections={toggledSections}
+          selectedNavInfo={selectedNavInfo}
+          key={key}
+          designDoc={designDoc}
+          designDocName={ddName}
+          database={this.props.database} />
       );
+    }.bind(this));
+  },
+
+  render: function () {
+    return (
+      <ul className="nav nav-list">
+        {this.designDocList()}
+      </ul>
+    );
+  }
+});
+
+var SidebarController = React.createClass({
+  getStoreState: function () {
+    return {
+      database: store.getDatabase(),
+      selectedNav: store.getSelected(),
+      designDocs: store.getDesignDocs(),
+      designDocList: store.getDesignDocList(),
+      availableDesignDocIds: store.getAvailableDesignDocs(),
+      toggledSections: store.getToggledSections(),
+      isLoading: store.isLoading(),
+      database: store.getDatabase(),
+      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()
+    };
+  },
+
+  getInitialState: function () {
+    return this.getStoreState();
+  },
+
+  componentDidMount: function () {
+    store.on('change', this.onChange, this);
+    deleteDbModalStore.on('change', this.onChange, this);
+  },
+
+  componentWillUnmount: function () {
+    store.off('change', this.onChange);
+    deleteDbModalStore.off('change', this.onChange, this);
+  },
+
+  onChange: function () {
+    if (this.isMounted()) {
+      this.setState(this.getStoreState());
+    }
+  },
+
+  showDeleteDatabaseModal: function (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: function () {
+
+    // 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
+    var 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: function () {
+    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
+    });
+  },
+
+  render: function () {
+    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>
+    );
+  }
+});
+
+
+var CloneIndexModal = React.createClass({
+  propTypes: {
+    visible: React.PropTypes.bool.isRequired,
+    title: React.PropTypes.string,
+    close: React.PropTypes.func.isRequired,
+    submit: React.PropTypes.func.isRequired,
+    designDocArray: React.PropTypes.array.isRequired,
+    selectedDesignDoc: React.PropTypes.string.isRequired,
+    newDesignDocName: React.PropTypes.string.isRequired,
+    newIndexName: React.PropTypes.string.isRequired,
+    indexLabel: React.PropTypes.string.isRequired
+  },
+
+  getDefaultProps: function () {
+    return {
+      title: 'Clone Index',
+      visible: false
+    };
+  },
+
+  submit: function () {
+    if (!this.refs.designDocSelector.validate()) {
+      return;
+    }
+    if (this.props.newIndexName === '') {
+      FauxtonAPI.addNotification({
+        msg: 'Please enter the new index name.',
+        type: 'error',
+        clear: true
+      });
+      return;
     }
-  });
+    this.props.submit();
+  },
 
-  return {
-    SidebarController: SidebarController,
-    DesignDoc: DesignDoc,
-    CloneIndexModal: CloneIndexModal
-  };
+  close: function (e) {
+    if (e) {
+      e.preventDefault();
+    }
+    this.props.close();
+  },
+
+  setNewIndexName: function (e) {
+    Actions.setNewCloneIndexName(e.target.value);
+  },
+
+  render: function () {
+    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="designDocSelector"
+                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="Enter 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-success save">
+            <i className="icon fonticon-ok-circled" /> Clone {this.props.indexLabel}</button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
 });
+
+export default {
+  SidebarController: SidebarController,
+  DesignDoc: DesignDoc,
+  CloneIndexModal: CloneIndexModal
+};


Mime
View raw message