You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ro...@apache.org on 2015/03/31 17:20:12 UTC

fauxton commit: updated refs/heads/master to d7641f1

Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master 4a40e3f9e -> d7641f16e


Mango: creation and listing of indexes

Part 1/2 for Mango:

Creation of Mango indexes and listing them. Disabled pagination
and bulk-deletion for now, see:

https://issues.apache.org/jira/browse/COUCHDB-2651
https://issues.apache.org/jira/browse/COUCHDB-2652

Use the direct urls to access the features:

http://localhost:8000/#database/$YOUR_DATABASE/_index
http://localhost:8000/#database/$YOUR_DATABASE/_indexlist

Additionally prepares the app for i18n.

Additionally removes the listing of Mango created indexes which
are not editable from the sidebar

COUCHDB-2627

PR: #343
PR-URL: https://github.com/apache/couchdb-fauxton/pull/343
Reviewed-By: garren smith <ga...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/d7641f16
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/d7641f16
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/d7641f16

Branch: refs/heads/master
Commit: d7641f16e7a12faff896136826b43f7affb0a393
Parents: 4a40e3f
Author: Robert Kowalski <ro...@apache.org>
Authored: Fri Mar 27 14:16:43 2015 +0100
Committer: Robert Kowalski <ro...@apache.org>
Committed: Tue Mar 31 17:20:15 2015 +0200

----------------------------------------------------------------------
 Gruntfile.js                                    |   7 +
 app/addons/components/assets/less/docs.less     |   6 +
 .../components/react-components.react.jsx       |  43 +++--
 app/addons/components/tests/docSpec.react.jsx   |  19 ++-
 app/addons/documents/base.js                    |  28 ++++
 app/addons/documents/index-editor/stores.js     |   4 +-
 app/addons/documents/index-results/actions.js   |   2 +-
 .../index-results.components.react.jsx          |  16 +-
 app/addons/documents/index-results/stores.js    |  29 +++-
 .../tests/index-results.actionsSpec.js          |   8 +
 .../tests/index-results.storesSpec.js           |  20 +--
 app/addons/documents/mango/mango.actions.js     |  58 +++++++
 app/addons/documents/mango/mango.actiontypes.js |  17 ++
 .../documents/mango/mango.components.react.jsx  | 156 +++++++++++++++++++
 app/addons/documents/mango/mango.stores.js      |  63 ++++++++
 .../mango/tests/mango.componentsSpec.react.jsx  |  93 +++++++++++
 app/addons/documents/resources.js               |  71 +++++++++
 app/addons/documents/routes-documents.js        |  29 +---
 app/addons/documents/routes-index-editor.js     |   4 +-
 app/addons/documents/routes-mango.js            | 154 ++++++++++++++++++
 app/addons/documents/routes.js                  |   9 +-
 app/addons/documents/shared-resources.js        |   8 +
 app/addons/documents/shared-routes.js           |  22 +++
 app/addons/documents/shared-views.js            |   5 +
 app/addons/documents/tests/headerSpec.react.jsx |   2 +-
 .../documents/tests/nightwatch/mangoIndex.js    |  48 ++++++
 .../tests/nightwatch/mangoIndexList.js          |  29 ++++
 app/addons/documents/tests/resourcesSpec.js     |  95 ++++++++++-
 .../tests/viewIndex.componentsSpec.react.jsx    |  87 ++++++++++-
 app/addons/documents/views-mango.js             |  66 ++++++++
 app/initialize.js.underscore                    |   3 +-
 i18n.json.default                               |   8 +
 package.json                                    |   1 +
 tasks/fauxton.js                                |  11 +-
 tasks/helper.js                                 |  11 ++
 .../custom-commands/populateDatabase.js         |  30 +++-
 36 files changed, 1164 insertions(+), 98 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/Gruntfile.js
----------------------------------------------------------------------
diff --git a/Gruntfile.js b/Gruntfile.js
index 781d97d..719f23c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -121,6 +121,13 @@ module.exports = function (grunt) {
     };
 
     var settings = helper.readSettingsFile();
+
+    var i18n = JSON.stringify(helper.readI18nFile(), null, ' ');
+
+    ['development', 'release', 'couchapp'].forEach(function (key) {
+      settings.template[key].app.i18n = i18n;
+    });
+
     return settings.template || defaultSettings;
   }();
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/components/assets/less/docs.less
----------------------------------------------------------------------
diff --git a/app/addons/components/assets/less/docs.less b/app/addons/components/assets/less/docs.less
index 97957c2..3f8a8f8 100644
--- a/app/addons/components/assets/less/docs.less
+++ b/app/addons/components/assets/less/docs.less
@@ -76,6 +76,12 @@
       padding-left: 23px; // 7px to the right-border + 16px around
     }
   }
+  .checkbox-dummy {
+    width: 20px;
+    height: 20px;
+    padding-left: 23px;
+    margin-right: 15px;
+  }
   .doc-item {
     width: auto;
     overflow: hidden;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/components/react-components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/components/react-components.react.jsx b/app/addons/components/react-components.react.jsx
index 91d04cd..26d27cd 100644
--- a/app/addons/components/react-components.react.jsx
+++ b/app/addons/components/react-components.react.jsx
@@ -65,30 +65,35 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) {
   var CodeEditor = React.createClass({
     render: function () {
       var code = this.aceEditor ? this.aceEditor.getValue() : this.props.code;
-      var docsLink;
-      if (this.props.docs) {
-        docsLink = <a
-                      className="help-link"
-                      data-bypass="true"
-                      href={this.props.docs}
-                      target="_blank"
-                    >
-                    <i className="icon-question-sign"></i>
-                   </a>;
-
-      }
       return (
         <div className="control-group">
-          <label htmlFor="ace-function">
-            <strong>{this.props.title}</strong>
-            {docsLink}
-          </label>
+          {this.getTitleFragment()}
           <div className="js-editor" id={this.props.id}>{this.props.code}</div>
           <Beautify code={code} beautifiedCode={this.setEditorValue} />
         </div>
       );
     },
 
+    getTitleFragment: function () {
+      if (!this.props.docs) {
+        return <strong>{this.props.title}</strong>;
+      }
+
+      return (
+        <label>
+          <strong>{this.props.title}</strong>
+          <a
+            className="help-link"
+            data-bypass="true"
+            href={this.props.docs}
+            target="_blank"
+          >
+          <i className="icon-question-sign"></i>
+          </a>;
+        </label>
+      );
+    },
+
     setEditorValue: function (code) {
       this.aceEditor.setValue(code);
       //this is not a good practice normally but because we working with a backbone view as the mapeditor
@@ -213,6 +218,11 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) {
     },
 
     getCheckbox: function () {
+
+      if (!this.props.isDeletable) {
+        return <div className="checkbox-dummy"></div>;
+      }
+
       return (
         <div className="checkbox inline">
           <input
@@ -286,7 +296,6 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) {
     }
   });
 
-
   var ReactComponents = {
     ConfirmButton: ConfirmButton,
     ToggleHeaderButton: ToggleHeaderButton,

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/components/tests/docSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/components/tests/docSpec.react.jsx b/app/addons/components/tests/docSpec.react.jsx
index 119abe6..be1f7ce 100644
--- a/app/addons/components/tests/docSpec.react.jsx
+++ b/app/addons/components/tests/docSpec.react.jsx
@@ -51,7 +51,7 @@ define([
 
     it('you can check it', function () {
       el = TestUtils.renderIntoDocument(
-        <ReactComponents.Document checked={true} docIdentifier="foo" />,
+        <ReactComponents.Document isDeletable={true} checked={true} docIdentifier="foo" />,
         container
       );
       assert.equal($(el.getDOMNode()).find('input[type="checkbox"]').attr('checked'), 'checked');
@@ -59,7 +59,7 @@ define([
 
     it('you can uncheck it', function () {
       el = TestUtils.renderIntoDocument(
-        <ReactComponents.Document docIdentifier="foo" />,
+        <ReactComponents.Document isDeletable={true} docIdentifier="foo" />,
         container
       );
       assert.equal($(el.getDOMNode()).find('input[type="checkbox"]').attr('checked'), undefined);
@@ -69,7 +69,7 @@ define([
       var spy = sinon.spy();
 
       el = TestUtils.renderIntoDocument(
-        <ReactComponents.Document docChecked={spy} docIdentifier="foo" />,
+        <ReactComponents.Document isDeletable={true} docChecked={spy} docIdentifier="foo" />,
         container
       );
       var testEl = $(el.getDOMNode()).find('input[type="checkbox"]')[0];
@@ -81,12 +81,23 @@ define([
       var spy = sinon.spy();
 
       el = TestUtils.renderIntoDocument(
-        <ReactComponents.Document onDoubleClick={spy} docIdentifier="foo" />,
+        <ReactComponents.Document isDeletable={true} onDoubleClick={spy} docIdentifier="foo" />,
         container
       );
       React.addons.TestUtils.Simulate.doubleClick(el.getDOMNode());
       assert.ok(spy.calledOnce);
     });
+
+    it('can render without checkbox', function () {
+      var spy = sinon.spy();
+
+      el = TestUtils.renderIntoDocument(
+        <ReactComponents.Document isDeletable={false} onDoubleClick={spy} docIdentifier="foo" />,
+        container
+      );
+      assert.notOk($(el.getDOMNode()).find('input[type="checkbox"]').length);
+      assert.ok($(el.getDOMNode()).find('.checkbox-dummy').length);
+    });
   });
 
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/base.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index 776f514..d6ac60d 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -110,5 +110,33 @@ function (app, FauxtonAPI, Documents) {
       return '/database/' + database + '/' ;
     },
   });
+
+  FauxtonAPI.registerUrls('mango', {
+
+    'index-server': function (db, query) {
+      if (!query) {
+        query = '';
+      }
+
+      return app.host + '/' + db + '/_index' + query;
+    },
+
+    'index-apiurl': function (db, query) {
+      if (!query) {
+        query = '';
+      }
+
+      return window.location.origin + '/' + db + '/_index' + query;
+    },
+
+    'index-app': function (db, query) {
+      if (!query) {
+        query = '';
+      }
+
+      return 'database/' + db + '/_index' + query;
+    }
+  });
+
   return Documents;
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-editor/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-editor/stores.js b/app/addons/documents/index-editor/stores.js
index 2070df3..f9a3606 100644
--- a/app/addons/documents/index-editor/stores.js
+++ b/app/addons/documents/index-editor/stores.js
@@ -71,7 +71,9 @@ function (FauxtonAPI, ActionTypes) {
     },
 
     getDesignDocs: function () {
-      return this._designDocs;
+      return this._designDocs.filter(function (ddoc) {
+        return ddoc.get('doc').language !== 'query';
+      });
     },
 
     getDesignDocId: function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/actions.js b/app/addons/documents/index-results/actions.js
index 88d57bf..0f91295 100644
--- a/app/addons/documents/index-results/actions.js
+++ b/app/addons/documents/index-results/actions.js
@@ -74,7 +74,7 @@ function (app, FauxtonAPI, ActionTypes, Stores, HeaderStores, HeaderActions, Doc
     reloadResultsList: function () {
       return this.newResultsList({
         collection: indexResultsStore.getCollection(),
-        deleteable: indexResultsStore.isDeleteable()
+        isListDeletable: indexResultsStore.isListDeletable()
       });
     },
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/index-results.components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/index-results.components.react.jsx b/app/addons/documents/index-results/index-results.components.react.jsx
index 61d1955..0d0d8d1 100644
--- a/app/addons/documents/index-results/index-results.components.react.jsx
+++ b/app/addons/documents/index-results/index-results.components.react.jsx
@@ -43,7 +43,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) {
     },
 
     getUrlFragment: function (url) {
-      if (this.props.hasReduce) {
+      if (!this.props.isEditable) {
         return null;
       }
 
@@ -55,6 +55,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) {
 
     getDocumentList: function () {
       return _.map(this.props.results, function (doc) {
+
         return (
          <Components.Document
            key={doc.id}
@@ -64,6 +65,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) {
            docContent={doc.content}
            checked={this.props.isSelected(doc.id)}
            docChecked={this.props.docChecked}
+           isDeletable={doc.isDeletable}
            docIdentifier={doc.id} >
            {this.getUrlFragment('#' + doc.url)}
          </Components.Document>
@@ -79,7 +81,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) {
         loadLines = <Components.LoadLines />;
       }
 
-      if (this.props.isDeleteable) {
+      if (this.props.isListDeletable) {
         classNames += ' show-select';
       }
 
@@ -111,10 +113,10 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) {
       return {
         hasResults: store.hasResults(),
         results: store.getResults(),
-        isDeleteable: store.isDeleteable(),
+        isListDeletable: store.isListDeletable(),
         isSelected: store.isSelected,
-        hasReduce: store.hasReduce(),
-        isLoading: store.isLoading()
+        isLoading: store.isLoading(),
+        isEditable: store.isEditable()
       };
     },
 
@@ -149,8 +151,8 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) {
         view = <ResultsScreen
           isCollapsed={this.isCollapsed}
           isSelected={this.isSelected}
-          hasReduce={this.state.hasReduce}
-          isDeleteable={this.state.isDeleteable}
+          isEditable={this.state.isEditable}
+          isListDeletable={this.state.isListDeletable}
           docChecked={this.docChecked}
           isLoading={this.state.isLoading}
           results={this.state.results} />;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/stores.js b/app/addons/documents/index-results/stores.js
index 605ee6f..52cf03e 100644
--- a/app/addons/documents/index-results/stores.js
+++ b/app/addons/documents/index-results/stores.js
@@ -27,7 +27,7 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) {
   Stores.IndexResultsStore = FauxtonAPI.Store.extend({
 
     initialize: function () {
-      this._deleteable = false;
+      this._isListDeletable = false;
       this._collection = [];
       this.clearSelectedItems();
       this.clearCollapsedDocs();
@@ -44,16 +44,29 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) {
 
     newResults: function (options) {
       this._collection = options.collection;
-      this._deleteable = options.deleteable;
+      this._isListDeletable = options.isListDeletable;
       this.clearSelectedItems();
       this.clearCollapsedDocs();
     },
 
-    hasReduce: function () {
-      if (!this._collection || !this._collection.params) {
+    isEditable: function (doc) {
+      if (!this._collection) {
         return false;
       }
-      return this._collection.params.reduce;
+
+      if (!this._collection.isEditable) {
+        return false;
+      }
+
+      return this._collection.isEditable();
+    },
+
+    isDeletable: function (doc) {
+      return doc.isDeletable();
+    },
+
+    isListDeletable: function () {
+      return this._isListDeletable;
     },
 
     getCollection: function () {
@@ -79,7 +92,7 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) {
         return doc.id;
       }
 
-      if (!_.isNull(doc.get('key'))) {
+      if (doc.get('key')) {
         return doc.get('key').toString();
       }
 
@@ -92,7 +105,9 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) {
           content: this.getDocContent(doc),
           id: this.getDocId(doc),
           keylabel: doc.isFromView() ? 'key' : 'id',
-          url: doc.isFromView() ? doc.url('app') : doc.url('web-index')
+          url: doc.isFromView() ? doc.url('app') : doc.url('web-index'),
+          isDeletable: this.isDeletable(doc),
+          isEditable: this.isEditable(doc),
         };
       }, this);
     },

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/tests/index-results.actionsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/tests/index-results.actionsSpec.js b/app/addons/documents/index-results/tests/index-results.actionsSpec.js
index 5ec9aab..1bbba77 100644
--- a/app/addons/documents/index-results/tests/index-results.actionsSpec.js
+++ b/app/addons/documents/index-results/tests/index-results.actionsSpec.js
@@ -153,6 +153,10 @@ define([
       };
       var stub = sinon.stub(store, 'createBulkDeleteFromSelected');
       stub.returns(bulkDelete);
+      var reloadResultsListStub = sinon.stub(Actions, 'reloadResultsList');
+      var stubPromise = FauxtonAPI.Deferred();
+      stubPromise.resolve();
+      reloadResultsListStub.returns(stubPromise);
 
       Actions.deleteSelected();
 
@@ -196,6 +200,10 @@ define([
       };
       var stub = sinon.stub(store, 'createBulkDeleteFromSelected');
       stub.returns(bulkDelete);
+      var reloadResultsListStub = sinon.stub(Actions, 'reloadResultsList');
+      var stubPromise = FauxtonAPI.Deferred();
+      stubPromise.resolve();
+      reloadResultsListStub.returns(stubPromise);
 
       Actions.deleteSelected();
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/tests/index-results.storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-results/tests/index-results.storesSpec.js b/app/addons/documents/index-results/tests/index-results.storesSpec.js
index 574082f..984d496 100644
--- a/app/addons/documents/index-results/tests/index-results.storesSpec.js
+++ b/app/addons/documents/index-results/tests/index-results.storesSpec.js
@@ -347,26 +347,22 @@ define([
     });
   });
 
-  describe('hasReduce', function () {
+  describe('isEditable', function () {
 
     it('returns false for no collection', function () {
       store._collection = null;
-      assert.notOk(store.hasReduce());
+      assert.notOk(store.isEditable());
     });
 
-    it('returns false for no params', function () {
+    it('returns false for empty collection', function () {
       store._collection = [];
-      assert.notOk(store.hasReduce());
+      assert.notOk(store.isEditable());
     });
 
-    it('returns true for reduce param', function () {
-      store._collection = [];
-      store._collection.param = {
-        reduce: true
-      };
-      assert.notOk(store.hasReduce());
-
+    it('delegates to collection', function () {
+      store._collection = {};
+      store._collection.isEditable = function () { return 'stub'; };
+      assert.equal(store.isEditable(), 'stub');
     });
-
   });
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/mango.actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/mango/mango.actions.js b/app/addons/documents/mango/mango.actions.js
new file mode 100644
index 0000000..d3df146
--- /dev/null
+++ b/app/addons/documents/mango/mango.actions.js
@@ -0,0 +1,58 @@
+// 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.
+
+define([
+  'app',
+  'api',
+  'addons/documents/resources',
+  'addons/documents/mango/mango.actiontypes',
+  'addons/documents/mango/mango.stores',
+  'addons/documents/index-results/actions'
+
+],
+function (app, FauxtonAPI, Documents, ActionTypes, Stores, IndexResultsActions) {
+  var store = Stores.mangoStore;
+
+  return {
+
+    setDatabase: function (options) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.MANGO_SET_DB,
+        options: options
+      });
+    },
+
+    saveQuery: function (options) {
+      var mangoIndex = new Documents.MangoIndex(JSON.parse(options.queryCode), {database: options.database});
+
+      FauxtonAPI.addNotification({
+        msg:  'Saving Index for Query...',
+        type: 'info',
+        clear: true
+      });
+
+      mangoIndex.save().then(function (res) {
+        var msg = res.result === 'created' ? 'Index created' : 'Index already exits',
+            url = FauxtonAPI.urls('mango', 'index-app', options.database.safeID());
+
+        FauxtonAPI.addNotification({
+          msg:  msg,
+          type: 'success',
+          clear: true
+        });
+
+        IndexResultsActions.reloadResultsList();
+      }.bind(this));
+
+    }
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/mango.actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/mango/mango.actiontypes.js b/app/addons/documents/mango/mango.actiontypes.js
new file mode 100644
index 0000000..112eacd
--- /dev/null
+++ b/app/addons/documents/mango/mango.actiontypes.js
@@ -0,0 +1,17 @@
+// 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.
+
+define([], function () {
+  return {
+    MANGO_SET_DB: 'MANGO_SET_DB',
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/mango.components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/mango/mango.components.react.jsx b/app/addons/documents/mango/mango.components.react.jsx
new file mode 100644
index 0000000..8764779
--- /dev/null
+++ b/app/addons/documents/mango/mango.components.react.jsx
@@ -0,0 +1,156 @@
+// 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.
+
+define([
+  'app',
+  'api',
+  'react',
+  'addons/documents/mango/mango.stores',
+  'addons/documents/mango/mango.actions',
+  'addons/components/react-components.react',
+
+  'plugins/prettify'
+],
+
+function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) {
+  var mangoStore = Stores.mangoStore;
+
+  var PaddedBorderedBox = ReactComponents.PaddedBorderedBox;
+  var CodeEditor = ReactComponents.CodeEditor;
+  var ConfirmButton = ReactComponents.ConfirmButton;
+
+  var HelpScreen = React.createClass({
+    render: function () {
+      return (
+        <div className="watermark-logo">
+          <h3>{this.props.title}</h3>
+          <div>
+            Create an Index to query it afterwards.<br/><br/>
+            The example on the left shows how to create an index for the field '_id'
+          </div>
+        </div>
+      );
+    }
+  });
+
+  var MangoIndexEditorController = React.createClass({
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    getStoreState: function () {
+      return {
+        queryCode: mangoStore.getQueryCode(),
+        database: mangoStore.getDatabase(),
+      };
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    componentDidMount: function () {
+      mangoStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function () {
+      mangoStore.off('change', this.onChange);
+    },
+
+    render: function () {
+      return (
+        <div className="editor-wrapper span5 scrollable">
+          <PaddedBorderedBox>
+            <div className="editor-description">{this.props.description}</div>
+          </PaddedBorderedBox>
+          <PaddedBorderedBox>
+            <strong>Database</strong>
+            <div className="db-title">{this.state.database.id}</div>
+          </PaddedBorderedBox>
+          <form className="form-horizontal" onSubmit={this.saveQuery}>
+            <PaddedBorderedBox>
+              <CodeEditor
+                id="query-field"
+                ref="indexQueryEditor"
+                title={'Index'}
+                docs={false}
+                code={this.state.queryCode} />
+            </PaddedBorderedBox>
+            <div className="padded-box">
+              <div className="control-group">
+                <ConfirmButton text="Create Index" />
+              </div>
+            </div>
+          </form>
+        </div>
+      );
+    },
+
+    getEditor: function () {
+      return this.refs.indexQueryEditor.getEditor();
+    },
+
+    hasValidCode: function () {
+      var editor = this.getEditor();
+      return editor.hadValidCode();
+    },
+
+    clearNotifications: function () {
+      var editor = this.getEditor();
+      editor.editSaved();
+    },
+
+    saveQuery: function (event) {
+      event.preventDefault();
+
+      if (!this.hasValidCode()) {
+        FauxtonAPI.addNotification({
+          msg:  'Please fix the Javascript errors and try again.',
+          type: 'error',
+          clear: true
+        });
+        return;
+      }
+
+      this.clearNotifications();
+
+      Actions.saveQuery({
+        database: this.state.database,
+        queryCode: this.refs.indexQueryEditor.getValue()
+      });
+    }
+  });
+
+  var Views = {
+    renderHelpScreen: function (el) {
+      React.render(
+        <HelpScreen title={app.i18n.en_US['mango-help-title']} />,
+        el
+      );
+    },
+    removeHelpScreen: function (el) {
+      React.unmountComponentAtNode(el);
+    },
+    renderMangoIndexEditor: function (el) {
+      React.render(
+        <MangoIndexEditorController description={app.i18n.en_US['mango-descripton']} />,
+        el
+      );
+    },
+    removeMangoIndexEditor: function (el) {
+      React.unmountComponentAtNode(el);
+    },
+    MangoIndexEditorController: MangoIndexEditorController
+  };
+
+  return Views;
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/mango.stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/mango/mango.stores.js b/app/addons/documents/mango/mango.stores.js
new file mode 100644
index 0000000..f72a046
--- /dev/null
+++ b/app/addons/documents/mango/mango.stores.js
@@ -0,0 +1,63 @@
+// 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.
+
+define([
+  'api',
+  'addons/documents/mango/mango.actiontypes'
+],
+
+function (FauxtonAPI, ActionTypes) {
+
+
+  var defaultQuery = '{\n' +
+      '  "index": {\n' +
+      '    "fields": ["_id"]\n' +
+      '  },\n' +
+      '  "type" : "json"\n' +
+      '}';
+
+  var Stores = {};
+
+  Stores.MangoStore = FauxtonAPI.Store.extend({
+
+    getQueryCode: function () {
+      return defaultQuery;
+    },
+
+    setDatabase: function (options) {
+      this._database = options.database;
+    },
+
+    getDatabase: function () {
+      return this._database;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+
+        case ActionTypes.MANGO_SET_DB:
+          this.setDatabase(action.options);
+          this.triggerChange();
+        break;
+
+      }
+    }
+
+  });
+
+  Stores.mangoStore = new Stores.MangoStore();
+
+  Stores.mangoStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.mangoStore.dispatch);
+
+  return Stores;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx b/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx
new file mode 100644
index 0000000..c50897b
--- /dev/null
+++ b/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx
@@ -0,0 +1,93 @@
+// 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.
+
+define([
+  'api',
+  'addons/documents/mango/mango.components.react',
+  'addons/documents/mango/mango.stores',
+  'addons/documents/mango/mango.actions',
+
+  'addons/documents/resources',
+  'addons/databases/resources',
+
+  'testUtils',
+  'react'
+], function (FauxtonAPI, Views, Stores, MangoActions, Resources, Databases, utils, React) {
+
+  var assert = utils.assert;
+  var TestUtils = React.addons.TestUtils;
+
+  var fakeData = [
+      {
+        ddoc: '_design/e4d338e5d6f047749f5399ab998b4fa04ba0c816',
+        def: {
+          fields: [{
+            '_id': 'asc'
+          }]
+        },
+        name: 'e4d338e5d6f047749f5399ab998b4fa04ba0c816',
+        type: 'json'
+      },
+      {
+        ddoc: null,
+        def: {
+          fields: [{
+            '_id': 'asc'
+          }]
+        },
+        name: '_all_docs',
+        type: 'special'
+      }
+    ];
+
+
+  describe('Mango IndexEditor', function () {
+    var database = new Databases.Model({id: 'testdb'}),
+        container,
+        editor;
+
+    beforeEach(function () {
+      container = document.createElement('div');
+      MangoActions.setDatabase({
+        database: database
+      });
+      $('body').append('<div id="query-field"></div>');
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+      $('#query-field').remove();
+    });
+
+    it('renders a default index definition', function () {
+      editor = TestUtils.renderIntoDocument(<Views.MangoIndexEditorController description="foo" />, container);
+      var $el = $(editor.getDOMNode());
+      var payload = JSON.parse($el.find('.js-editor').text());
+      assert.equal(payload.index.fields[0], '_id');
+    });
+
+    it('renders the current database', function () {
+      editor = TestUtils.renderIntoDocument(<Views.MangoIndexEditorController description="foo" />, container);
+      var $el = $(editor.getDOMNode());
+
+      assert.equal($el.find('.db-title').text(), 'testdb');
+    });
+
+    it('renders a description', function () {
+      editor = TestUtils.renderIntoDocument(<Views.MangoIndexEditorController description="CouchDB Query is great!" />, container);
+      var $el = $(editor.getDOMNode());
+
+      assert.equal($el.find('.editor-description').text(), 'CouchDB Query is great!');
+    });
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js
index e6ee676..00559b9 100644
--- a/app/addons/documents/resources.js
+++ b/app/addons/documents/resources.js
@@ -72,6 +72,73 @@ function (app, FauxtonAPI, Documents, PagingCollection) {
     }
   });
 
+  Documents.MangoIndex = Documents.Doc.extend({
+    idAttribute: 'name',
+
+    isNew: function () {
+      // never use put
+      return true;
+    },
+
+    isDeletable: function () {
+      return this.get('type') !== 'special';
+    },
+
+    isFromView: function () {
+      return false;
+    },
+
+    url: function () {
+      var database = this.database.safeID();
+
+      return FauxtonAPI.urls('mango', 'index-server', database);
+    }
+  });
+
+  Documents.MangoIndexCollection = PagingCollection.extend({
+    model: Documents.MangoIndex,
+    initialize: function (_attr, options) {
+      var defaultLimit = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE;
+
+      this.database = options.database;
+      this.params = _.extend({limit: defaultLimit}, options.params);
+    },
+
+    url: function () {
+      return this.urlRef.apply(this, arguments);
+    },
+
+    updateSeq: function () {
+      return false;
+    },
+
+    isEditable: function () {
+      return false;
+    },
+
+    parse: function (res) {
+      return res.indexes;
+    },
+
+    urlRef: function (params) {
+      var database = this.database.safeID(),
+          query = '';
+
+      if (params) {
+        if (!_.isEmpty(params)) {
+          query = '?' + $.param(params);
+        } else {
+          query = '';
+        }
+      } else if (this.params) {
+        var parsedParam = Documents.QueryParams.stringify(this.params);
+        query = '?' + $.param(parsedParam);
+      }
+
+      return FauxtonAPI.urls('mango', 'index-apiurl', database, query);
+    }
+  });
+
   Documents.NewDoc = Documents.Doc.extend({
     fetch: function () {
       var uuid = new FauxtonAPI.UUID();
@@ -206,6 +273,10 @@ function (app, FauxtonAPI, Documents, PagingCollection) {
       }
     },
 
+    isEditable: function () {
+      return !this.params.reduce;
+    },
+
     urlRef: function (params) {
       var query = "";
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/routes-documents.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index ff57dac..2cf8907 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -20,6 +20,7 @@ define([
   'addons/documents/views-changes',
   'addons/documents/views-index',
   'addons/documents/views-doceditor',
+  'addons/documents/views-mango',
 
   'addons/databases/base',
   'addons/documents/resources',
@@ -28,7 +29,7 @@ define([
   'addons/documents/index-results/actions'
 ],
 
-function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor,
+function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mango,
   Databases, Resources, Components, PaginationStores, IndexResultsActions) {
 
 
@@ -44,6 +45,7 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor,
           roles: ['fx_loggedIn']
         },
         'database/:database/_changes': 'changes'
+
       },
 
       events: {
@@ -77,29 +79,6 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor,
         this.addSidebar();
       },
 
-      getAllDatabases: function () {
-        return new Databases.List();  //getAllDatabases() can be overwritten instead of hard coded into initViews
-      },
-
-      // this safely assumes the db name is valid
-      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);
-      },
-
       designDocMetadata: function (database, ddoc) {
         this.footer && this.footer.remove();
         this.toolsView && this.toolsView.remove();
@@ -159,7 +138,7 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor,
 
         IndexResultsActions.newResultsList({
           collection: collection,
-          deleteable: true
+          isListDeletable: true
         });
 
         this.database.allDocs.paging.pageSize = PaginationStores.indexPaginationStore.getPerPage();

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/routes-index-editor.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index 38c9566..4d1e3b3 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -92,7 +92,7 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index,
 
       IndexResultsActions.newResultsList({
         collection: this.indexedDocs,
-        deleteable: false
+        isListDeletable: false
       });
 
       this.viewEditor = this.setView('#left-content', new Index.ViewEditorReact({
@@ -142,7 +142,7 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index,
       this.resultList = this.setView('#dashboard-lower-content', new Index.ViewResultListReact({}));
       IndexResultsActions.newResultsList({
         collection: [],
-        deleteable: false
+        isListDeletable: false
       });
     }
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/routes-mango.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js
new file mode 100644
index 0000000..baade58
--- /dev/null
+++ b/app/addons/documents/routes-mango.js
@@ -0,0 +1,154 @@
+// 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.
+
+define([
+  'app',
+  'api',
+
+  // Modules
+  'addons/documents/helpers',
+  'addons/documents/shared-routes',
+  'addons/documents/views-mango',
+  'addons/databases/resources',
+  'addons/fauxton/components',
+  'addons/documents/resources',
+  'addons/documents/views',
+
+
+  'addons/documents/index-results/actions',
+  'addons/documents/pagination/stores',
+
+],
+
+function (app, FauxtonAPI, Helpers, BaseRoute, Mango, Databases,
+  Components, Resources, Documents, IndexResultsActions, PaginationStores) {
+
+  var MangoIndexList = BaseRoute.extend({
+    layout: 'with_tabs_sidebar',
+    routes: {
+      'database/:database/_indexlist(:extra)': {
+        route: 'mangoIndexList',
+        roles: ['fx_loggedIn']
+      },
+
+    },
+
+    establish: function () {
+      return [
+        this.designDocs.fetch({reset: true}),
+        this.allDatabases.fetchOnce()
+      ];
+    },
+
+    initialize: function (route, masterLayout, options) {
+      var databaseName = options[0];
+      this.databaseName = databaseName;
+      this.database = new Databases.Model({id: databaseName});
+
+      // magic methods
+      this.allDatabases = this.getAllDatabases();
+      this.createDesignDocsCollection();
+      this.addLeftHeader();
+      this.addSidebar();
+
+      this.rightHeader = this.setView('#right-header', new Documents.Views.RightAllDocsHeader({
+        database: this.database
+      }));
+    },
+
+    mangoIndexList: function () {
+      var params = this.createParams(),
+          urlParams = params.urlParams,
+          mangoIndexCollection = new Resources.MangoIndexCollection(null, {
+            database: this.database,
+            params: null,
+            paging: {
+              pageSize: PaginationStores.indexPaginationStore.getPerPage()
+            }
+          });
+
+      this.viewEditor && this.viewEditor.remove();
+      this.headerView && this.headerView.remove();
+
+      this.sidebar.setSelectedTab('mango-indexes');
+
+      IndexResultsActions.newResultsList({
+        collection: mangoIndexCollection,
+        isListDeletable: false
+      });
+
+      this.reactHeader = this.setView('#react-headerbar', new Documents.Views.ReactHeaderbar());
+
+      this.leftheader.updateCrumbs(this.getCrumbs(this.database));
+      this.rightHeader.hideQueryOptions();
+
+      this.resultList = this.setView('#dashboard-lower-content', new Mango.MangoIndexListReact());
+
+      this.apiUrl = function () {
+        return [mangoIndexCollection.urlRef(urlParams), FauxtonAPI.constants.DOC_URLS.GENERAL];
+      };
+    }
+  });
+
+  var MangoIndexEditorAndResults = BaseRoute.extend({
+    layout: 'two_pane',
+    routes: {
+      'database/:database/_index': {
+        route: 'createIndex',
+        roles: ['fx_loggedIn']
+      }
+    },
+
+    initialize: function (route, masterLayout, options) {
+      var databaseName = options[0];
+
+      this.databaseName = databaseName;
+      this.database = new Databases.Model({id: databaseName});
+    },
+
+    createIndex: function (database) {
+      var params = this.createParams(),
+          urlParams = params.urlParams,
+          mangoIndexCollection = new Resources.MangoIndexCollection(null, {
+            database: this.database
+          });
+
+      IndexResultsActions.newResultsList({
+        collection: mangoIndexCollection,
+        isListDeletable: false
+      });
+
+      this.breadcrumbs = this.setView('#breadcrumbs', new Components.Breadcrumbs({
+        toggleDisabled: true,
+        crumbs: [
+          {'type': 'back', 'link': Helpers.getPreviousPage(this.database)},
+          {'name': 'Create new index', 'link': Databases.databaseUrl(this.database) }
+        ]
+      }));
+
+      this.resultList = this.setView('#dashboard-lower-content', new Mango.HelpScreen());
+
+      this.mangoEditor = this.setView('#left-content', new Mango.MangoIndexEditorReact({
+        database: this.database
+      }));
+
+      this.apiUrl = function () {
+        return [mangoIndexCollection.urlRef(urlParams), FauxtonAPI.constants.DOC_URLS.GENERAL];
+      };
+    }
+  });
+
+  return {
+    MangoIndexEditorAndResults: MangoIndexEditorAndResults,
+    MangoIndexList: MangoIndexList
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes.js b/app/addons/documents/routes.js
index 0f16d4a..ad92ded 100644
--- a/app/addons/documents/routes.js
+++ b/app/addons/documents/routes.js
@@ -14,16 +14,19 @@ define([
   "addons/documents/views",
   "addons/documents/routes-documents",
   'addons/documents/routes-doc-editor',
-  'addons/documents/routes-index-editor'
+  'addons/documents/routes-index-editor',
+  'addons/documents/routes-mango'
 ],
 
-function (Documents, DocumentsRouteObject, docEditor, IndexEditorRouteObject) {
 
+function (Documents, DocumentsRouteObject, docEditor, IndexEditorRouteObject, Mango) {
   Documents.RouteObjects = [
     docEditor.DocEditorRouteObject,
     docEditor.NewDocEditorRouteObject,
     DocumentsRouteObject,
-    IndexEditorRouteObject
+    IndexEditorRouteObject,
+    Mango.MangoIndexList,
+    Mango.MangoIndexEditorAndResults
   ];
 
   return Documents;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/shared-resources.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/shared-resources.js b/app/addons/documents/shared-resources.js
index 04098c0..4800480 100644
--- a/app/addons/documents/shared-resources.js
+++ b/app/addons/documents/shared-resources.js
@@ -57,6 +57,10 @@ define([
       return this.id && this.id.match(/^_design\//) ? "design doc" : "doc";
     },
 
+    isDeletable: function () {
+      return true;
+    },
+
     isFromView: function () {
       return !this.id;
     },
@@ -215,6 +219,10 @@ define([
       }
     },
 
+    isEditable: function () {
+      return true;
+    },
+
     urlRef: function (context, params) {
       var query = "";
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/shared-routes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/shared-routes.js b/app/addons/documents/shared-routes.js
index 4766203..9a59afd 100644
--- a/app/addons/documents/shared-routes.js
+++ b/app/addons/documents/shared-routes.js
@@ -43,6 +43,28 @@ define([
       });
     },
 
+    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,

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/shared-views.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/shared-views.js b/app/addons/documents/shared-views.js
index 93876a7..627c682 100644
--- a/app/addons/documents/shared-views.js
+++ b/app/addons/documents/shared-views.js
@@ -118,6 +118,11 @@ function (app, FauxtonAPI, Components, Documents, Databases) {
       this.designDocList = [];
 
       this.collection.each(function (design) {
+
+        if (design.get('doc').language === 'query') {
+          return;
+        }
+
         if (design.has('doc')) {
           design.collection = this.collection;
           var view = this.insertView(new Views.DdocSidenav({

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/headerSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/headerSpec.react.jsx b/app/addons/documents/tests/headerSpec.react.jsx
index 33528c5..420aebe 100644
--- a/app/addons/documents/tests/headerSpec.react.jsx
+++ b/app/addons/documents/tests/headerSpec.react.jsx
@@ -83,7 +83,7 @@ define([
 
       IndexResultsActions.newResultsList({
         collection: database.allDocs,
-        deleteable: false
+        isListDeletable: false
       });
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/nightwatch/mangoIndex.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/nightwatch/mangoIndex.js b/app/addons/documents/tests/nightwatch/mangoIndex.js
new file mode 100644
index 0000000..1c66ebe
--- /dev/null
+++ b/app/addons/documents/tests/nightwatch/mangoIndex.js
@@ -0,0 +1,48 @@
+// 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.
+
+module.exports = {
+
+  'Creating new indexes with mango': function (client) {
+    /*jshint multistr: true */
+    var waitTime = 10000,
+        newDatabaseName = client.globals.testDatabaseName,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .populateDatabase(newDatabaseName)
+      .loginToGUI()
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_index')
+      .waitForElementPresent('.watermark-logo', waitTime, false)
+      .assert.containsText('.watermark-logo', 'Mango')
+      .assert.containsText('.editor-description', 'is an easy way to find documents on predefined indexes')
+      .execute('\
+        var json = \'{\
+          "index": {\
+            "fields": ["ente_ente_mango"]\
+          },\
+          "name": "rocko-artischocko",\
+          "type" : "json"\
+        }\';\
+        var editor = ace.edit("query-field");\
+        editor.getSession().setValue(json);\
+      ')
+      .execute('$(".save")[0].scrollIntoView();')
+      .click('button.btn-success.save')
+
+      .waitForElementNotVisible('.global-notification', waitTime, false)
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_indexlist')
+      .waitForElementPresent('.prettyprint', waitTime, false)
+      .assert.containsText('#dashboard-lower-content', 'ente_ente_mango')
+    .end();
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/nightwatch/mangoIndexList.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/nightwatch/mangoIndexList.js b/app/addons/documents/tests/nightwatch/mangoIndexList.js
new file mode 100644
index 0000000..a4f8cc0
--- /dev/null
+++ b/app/addons/documents/tests/nightwatch/mangoIndexList.js
@@ -0,0 +1,29 @@
+// 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.
+
+module.exports = {
+
+  'Creating new indexes with mango': function (client) {
+    var waitTime = 10000,
+        newDatabaseName = client.globals.testDatabaseName,
+        baseUrl = client.globals.test_settings.launch_url;
+
+    client
+      .populateDatabase(newDatabaseName)
+      .loginToGUI()
+      .url(baseUrl + '/#/database/' + newDatabaseName + '/_indexlist')
+      .waitForElementPresent('.prettyprint', waitTime, false)
+      .assert.containsText('.header-doc-id', '_all_docs')
+      .assert.containsText('#doc-list', 'ente_ente_mango_ananas')
+    .end();
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/resourcesSpec.js b/app/addons/documents/tests/resourcesSpec.js
index 11181ef..8d0e0c8 100644
--- a/app/addons/documents/tests/resourcesSpec.js
+++ b/app/addons/documents/tests/resourcesSpec.js
@@ -71,21 +71,110 @@ define([
     });
   });
 
+  describe('MangoIndex', function () {
+    var doc;
+
+    it('is deleteable', function () {
+      var index = {
+        ddoc: null,
+        name: '_all_docs',
+        type: 'json',
+        def: {fields: [{_id: 'asc'}]}
+      };
+      doc = new Models.MangoIndex(index, {});
+
+      assert.ok(doc.isDeletable());
+    });
+
+    it('special docs are not deleteable', function () {
+      var index = {
+        ddoc: null,
+        name: '_all_docs',
+        type: 'special',
+        def: {fields: [{_id: 'asc'}]}
+      };
+      doc = new Models.MangoIndex(index, {});
+
+      assert.notOk(doc.isDeletable());
+    });
+  });
+
+  describe('MangoIndexCollection', function () {
+    var collection;
+
+    it('is not editable', function () {
+      collection = new Models.MangoIndexCollection([{
+        name: 'myId1',
+        doc: 'num1'
+      },
+      {
+        name: 'myId2',
+        doc: 'num2'
+      }], {
+        database: {id: 'databaseId', safeID: function () { return this.id; }},
+        params: {limit: 20}
+      });
+
+      assert.notOk(collection.isEditable());
+    });
+  });
+
+
+  describe('IndexCollection', function () {
+    var collection;
+
+    it('design docs are editable', function () {
+      collection = new Models.IndexCollection([{
+        _id: 'myId1',
+        doc: 'num1'
+      },
+      {
+        _id: 'myId2',
+        doc: 'num2'
+      }], {
+        database: {id: 'databaseId', safeID: function () { return this.id; }},
+        params: {limit: 20},
+        design: '_design/foobar'
+      });
+
+      assert.ok(collection.isEditable());
+    });
+
+    it('reduced design docs are NOT editable', function () {
+      collection = new Models.IndexCollection([{
+        _id: 'myId1',
+        doc: 'num1'
+      },
+      {
+        _id: 'myId2',
+        doc: 'num2'
+      }], {
+        database: {id: 'databaseId', safeID: function () { return this.id; }},
+        params: {limit: 20, reduce: true},
+        design: '_design/foobar'
+      });
+
+      assert.notOk(collection.isEditable());
+    });
+  });
+
   describe('AllDocs', function () {
     var collection;
-    beforeEach(function () {
+
+    it('all-docs-list documents are always editable', function () {
       collection = new Models.AllDocs([{
-        _id:'myId1',
+        _id: 'myId1',
         doc: 'num1'
       },
       {
-        _id:'myId2',
+        _id: 'myId2',
         doc: 'num2'
       }], {
         database: {id: 'databaseId', safeID: function () { return this.id; }},
         params: {limit: 20}
       });
 
+      assert.ok(collection.isEditable());
     });
   });
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx b/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx
index 659cc8e..68c4033 100644
--- a/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx
+++ b/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx
@@ -23,8 +23,20 @@ define([
   var assert = utils.assert;
   var TestUtils = React.addons.TestUtils;
 
-  var resetStore = function (designDoc) {
-    var designDocs = new Documents.AllDocs([designDoc], {
+  var resetStore = function (designDocs) {
+    designDocs = designDocs.map(function (doc) {
+      return Documents.Doc.prototype.parse(doc);
+    });
+
+    designDocs.map(function (ddoc) {
+      return new Documents.Doc(ddoc, {
+        database: {
+          safeID: function () { return 'id'; }
+        }
+      });
+    });
+
+    var ddocs = new Documents.AllDocs(designDocs, {
       params: { limit: 10 },
       database: {
         safeID: function () { return 'id';}
@@ -35,8 +47,8 @@ define([
       database: {id: 'rockos-db'},
       newView: false,
       viewName: 'test-view',
-      designDocs: designDocs,
-      designDocId: designDoc._id
+      designDocs: ddocs,
+      designDocId: designDocs[0]._id
     });
   };
 
@@ -72,7 +84,7 @@ define([
           }
         };
 
-        resetStore(designDoc);
+        resetStore([designDoc]);
 
         reduceEl = TestUtils.renderIntoDocument(<Views.ReduceEditor/>, container);
         assert.ok(_.isNull(reduceEl.getReduceValue()));
@@ -91,7 +103,7 @@ define([
           }
         };
 
-        resetStore(designDoc);
+        resetStore([designDoc]);
 
         reduceEl = TestUtils.renderIntoDocument(<Views.ReduceEditor/>, container);
         assert.equal(reduceEl.getReduceValue(), '_sum');
@@ -107,6 +119,61 @@ define([
       container = document.createElement('div');
       $('body').append('<div id="map-function"></div>');
       $('body').append('<div id="editor"></div>');
+      var designDoc = {
+        "id": "_design/test-doc",
+        "key": "_design/test-doc",
+        "value": {
+          "rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80"
+        },
+        "doc": {
+          "_id": "_design/test-doc",
+          "_rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80",
+          "views": {
+            "test-view": {
+              "map": "function(doc) {\n  emit(doc._id, 2);\n}"
+            },
+            "new-view": {
+              "map": "function(doc) {\n  if (doc.class === \"mammal\" && doc.diet === \"herbivore\")\n    emit(doc._id, 1);\n}",
+              "reduce": "_sum"
+            }
+          },
+          "language": "javascript",
+          "indexes": {
+            "newSearch": {
+              "analyzer": "standard",
+              "index": "function(doc){\n index(\"default\", doc._id);\n}"
+            }
+          }
+        }
+      };
+      var mangodoc = {
+        "id": "_design/123mango",
+        "key": "_design/123mango",
+        "value": {
+          "rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80"
+        },
+        "doc": {
+          "_id": "_design/123mango",
+          "_rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80",
+          "views": {
+            "test-view": {
+              "map": "function(doc) {\n  emit(doc._id, 2);\n}"
+            },
+            "new-view": {
+              "map": "function(doc) {\n  if (doc.class === \"mammal\" && doc.diet === \"herbivore\")\n    emit(doc._id, 1);\n}",
+              "reduce": "_sum"
+            }
+          },
+          "language": "query",
+          "indexes": {
+            "newSearch": {
+              "analyzer": "standard",
+              "index": "function(doc){\n index(\"default\", doc._id);\n}"
+            }
+          }
+        }
+      };
+      resetStore([designDoc, mangodoc]);
       selectorEl = TestUtils.renderIntoDocument(<Views.DesignDocSelector/>, container);
     });
 
@@ -151,6 +218,14 @@ define([
       assert.ok(spy.calledWith('_design/new-doc-entered', true));
     });
 
+    it('does not filter usual design docs', function () {
+      assert.ok(/_design\/test-doc/.test($(selectorEl.getDOMNode()).text()));
+    });
+
+    it('filters mango docs', function () {
+      selectorEl = TestUtils.renderIntoDocument(<Views.DesignDocSelector/>, container);
+      assert.notOk(/_design\/123mango/.test($(selectorEl.getDOMNode()).text()));
+    });
   });
 
   describe('Editor', function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/views-mango.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views-mango.js b/app/addons/documents/views-mango.js
new file mode 100644
index 0000000..7b45984
--- /dev/null
+++ b/app/addons/documents/views-mango.js
@@ -0,0 +1,66 @@
+// 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.
+
+define([
+  'api',
+  'addons/documents/mango/mango.components.react',
+  'addons/documents/mango/mango.actions',
+  'addons/documents/index-results/index-results.components.react'
+],
+
+function (FauxtonAPI, Mango, MangoActions, ViewResultList) {
+
+  var Views = {};
+
+
+  Views.HelpScreen = FauxtonAPI.View.extend({
+
+    afterRender: function () {
+      Mango.renderHelpScreen(this.el);
+    },
+
+    cleanup: function () {
+      Mango.removeHelpScreen(this.el);
+    }
+  });
+
+  Views.MangoIndexListReact = FauxtonAPI.View.extend({
+
+    afterRender: function () {
+      ViewResultList.renderViewResultList(this.el);
+    },
+
+    cleanup: function () {
+      ViewResultList.removeViewResultList(this.el);
+    }
+  });
+
+  Views.MangoIndexEditorReact = FauxtonAPI.View.extend({
+    initialize: function (options) {
+      this.database = options.database;
+    },
+
+    afterRender: function () {
+      MangoActions.setDatabase({
+        database: this.database
+      });
+
+      Mango.renderMangoIndexEditor(this.el);
+    },
+
+    cleanup: function () {
+      Mango.removeMangoIndexEditor(this.el);
+    }
+  });
+
+  return Views;
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/initialize.js.underscore
----------------------------------------------------------------------
diff --git a/app/initialize.js.underscore b/app/initialize.js.underscore
index c0af5fb..0f6c36f 100644
--- a/app/initialize.js.underscore
+++ b/app/initialize.js.underscore
@@ -26,7 +26,8 @@ function () {
     version: "<%= version %>",
     // Host is used as prefix for urls
     host: "<%= host %>",
-    zeroClipboardPath: "<%= zeroClipboardPath %>"
+    zeroClipboardPath: "<%= zeroClipboardPath %>",
+    i18n: <%= i18n %>
   };
 
   return app;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/i18n.json.default
----------------------------------------------------------------------
diff --git a/i18n.json.default b/i18n.json.default
new file mode 100644
index 0000000..aa89b17
--- /dev/null
+++ b/i18n.json.default
@@ -0,0 +1,8 @@
+{
+  "en_US": {
+    "mango-descripton": "Mango is an easy way to find documents on predefined indexes.",
+    "all-mango-indexes": "All Mango Indexes",
+    "new-mango-index": "New Mango Index",
+    "mango-help-title": "Mango"
+  }
+}

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index e9b13ea..cd8efe1 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
     "nano": "~5.12.0",
     "nightwatch": "~0.5.33",
     "react-tools": "^0.12.0",
+    "request": "^2.54.0",
     "send": "~0.1.1",
     "underscore": "~1.4.2",
     "url": "~0.7.9",

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/tasks/fauxton.js
----------------------------------------------------------------------
diff --git a/tasks/fauxton.js b/tasks/fauxton.js
index 743c8ef..ee73399 100644
--- a/tasks/fauxton.js
+++ b/tasks/fauxton.js
@@ -92,12 +92,11 @@ module.exports = function (grunt) {
 
   grunt.registerMultiTask('gen_initialize', 'Generate the app.js file', function () {
     var _ = grunt.util._,
-      settings = this.data,
-      template = "app/initialize.js.underscore",
-      dest = "app/initialize.js",
-      tmpl = _.template(grunt.file.read(template)),
-      app = {};
-
+        settings = this.data,
+        template = "app/initialize.js.underscore",
+        dest = "app/initialize.js",
+        tmpl = _.template(grunt.file.read(template)),
+        app = {};
 
     _.defaults(app, settings.app, {
       root: '/',

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/tasks/helper.js
----------------------------------------------------------------------
diff --git a/tasks/helper.js b/tasks/helper.js
index e0b82bb..abfc01f 100644
--- a/tasks/helper.js
+++ b/tasks/helper.js
@@ -28,6 +28,17 @@ exports.init = function (grunt) {
       }
     },
 
+    readI18nFile: function () {
+      if (fs.existsSync('i18n.json')) {
+        return grunt.file.readJSON('i18n.json');
+      }
+      if (fs.existsSync('i18n.json.default')) {
+        return grunt.file.readJSON('i18n.json.default');
+      }
+
+      throw new Error('i18n file missing');
+    },
+
     processAddons: function (callback) {
       this.readSettingsFile().deps.forEach(callback);
     },

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/test/nightwatch_tests/custom-commands/populateDatabase.js
----------------------------------------------------------------------
diff --git a/test/nightwatch_tests/custom-commands/populateDatabase.js b/test/nightwatch_tests/custom-commands/populateDatabase.js
index f17df8f..b5cd161 100644
--- a/test/nightwatch_tests/custom-commands/populateDatabase.js
+++ b/test/nightwatch_tests/custom-commands/populateDatabase.js
@@ -13,7 +13,8 @@
 var util = require('util'),
     events = require('events'),
     helpers = require('../helpers/helpers.js'),
-    async = require('async');
+    async = require('async'),
+    request = require('request');
 
 function PopulateDatabase () {
   events.EventEmitter.call(this);
@@ -56,7 +57,9 @@ PopulateDatabase.prototype.command = function (databaseName, count) {
 
         createKeyView(null, function () {
           createBrokenView(null, function () {
-            that.emit('complete');
+            createMangoIndex(null, function () {
+              that.emit('complete');
+            });
           });
         });
       });
@@ -97,6 +100,29 @@ PopulateDatabase.prototype.command = function (databaseName, count) {
       cb();
     });
   }
+
+  function createMangoIndex (err, cb) {
+    request({
+      uri: helpers.test_settings.db_url + '/' + databaseName + '/_index',
+      method: 'POST',
+      json: true,
+      body: {
+        index: {
+          fields: ['ente_ente_mango_ananas']
+        },
+        name: 'rocko-artischockbert',
+        type: 'json'
+      }
+    }, function (err, res, body) {
+      if (err) {
+        console.log('Error in nano populateDatabase Function: ' +
+          err.message);
+      }
+
+      cb && cb();
+    });
+  }
+
   return this;
 };