couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gar...@apache.org
Subject [24/27] fauxton commit: updated refs/heads/master to 0ca35da
Date Tue, 31 May 2016 07:58:52 GMT
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/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 7e05476..f963dbb 100644
--- a/app/addons/components/react-components.react.jsx
+++ b/app/addons/components/react-components.react.jsx
@@ -10,1624 +10,1617 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../app',
-  '../../core/api',
-  'react',
-  'react-dom',
-  './stores',
-  './actions',
-  '../fauxton/components.react',
-  '../documents/helpers',
-  '../../../assets/js/plugins/beautify',
-  'react-bootstrap',
-  'react-addons-css-transition-group',
-  'brace'
-],
-  function (app, FauxtonAPI, React, ReactDOM, Stores, Actions, FauxtonComponents, Helpers,
-	   beautifyHelper, ReactBootstrap, ReactCSSTransitionGroup, ace) {
-
-  var componentStore = Stores.componentStore;
-  var Modal = ReactBootstrap.Modal;
-
-  var BadgeList = React.createClass({
-
-    propTypes: {
-      elements: React.PropTypes.array.isRequired,
-      removeBadge: React.PropTypes.func.isRequired
-    },
+import app from "../../app";
+import FauxtonAPI from "../../core/api";
+import React from "react";
+import ReactDOM from "react-dom";
+import Stores from "./stores";
+import Actions from "./actions";
+import FauxtonComponents from "../fauxton/components.react";
+import Helpers from "../documents/helpers";
+import beautifyHelper from "../../../assets/js/plugins/beautify";
+import {Modal, Popover, OverlayTrigger} from "react-bootstrap";
+import ReactCSSTransitionGroup from "react-addons-css-transition-group";
+import ace from "brace";
+
+const { componentStore } = Stores;
+
+var BadgeList = React.createClass({
+
+  propTypes: {
+    elements: React.PropTypes.array.isRequired,
+    removeBadge: React.PropTypes.func.isRequired
+  },
+
+  getDefaultProps: function () {
+    return {
+      getLabel: function (el) {
+        return el;
+      },
 
-    getDefaultProps: function () {
-      return {
-        getLabel: function (el) {
-          return el;
-        },
+      getId: function (el) {
+        return el;
+      }
 
-        getId: function (el) {
-          return el;
-        }
+    };
+  },
 
-      };
-    },
+  getBadges: function () {
+    return this.props.elements.map(function (el, i) {
+      return <Badge
+        label={this.props.getLabel(el)}
+        key={i}
+        id={el}
+        remove={this.removeBadge} />;
+    }.bind(this));
+  },
 
-    getBadges: function () {
-      return this.props.elements.map(function (el, i) {
-        return <Badge
-          label={this.props.getLabel(el)}
-          key={i}
-          id={el}
-          remove={this.removeBadge} />;
-      }.bind(this));
-    },
+  removeBadge: function (label, el) {
+    this.props.removeBadge(label, el);
+  },
 
-    removeBadge: function (label, el) {
-      this.props.removeBadge(label, el);
-    },
+  render: function () {
+    return (
+      <ul className="component-badgelist">
+        {this.getBadges()}
+      </ul>
+    );
+  }
+});
 
-    render: function () {
-      return (
-        <ul className="component-badgelist">
-          {this.getBadges()}
-        </ul>
-      );
-    }
-  });
+var Badge = React.createClass({
+  propTypes: {
+    label: React.PropTypes.string.isRequired,
+    remove: React.PropTypes.func.isRequired
+  },
 
-  var Badge = React.createClass({
-    propTypes: {
-      label: React.PropTypes.string.isRequired,
-      remove: React.PropTypes.func.isRequired
-    },
+  remove: function (e) {
+    e.preventDefault();
+    this.props.remove(this.props.label, this.props.id);
+  },
 
-    remove: function (e) {
-      e.preventDefault();
-      this.props.remove(this.props.label, this.props.id);
-    },
+  render: function () {
+    return (
+      <li className="component-badge">
+        <span className="label label-info">{this.props.label}</span>
+        <a
+          href="#"
+          className="label label-info js-remove-filter"
+          onClick={this.remove} data-bypass="true"
+        >
+          &times;
+        </a>
+      </li>
+    );
+  }
+});
 
-    render: function () {
-      return (
-        <li className="component-badge">
-          <span className="label label-info">{this.props.label}</span>
-          <a
-            href="#"
-            className="label label-info js-remove-filter"
-            onClick={this.remove} data-bypass="true"
-          >
-            &times;
-          </a>
-        </li>
-      );
+var ToggleHeaderButton = React.createClass({
+  getDefaultProps: function () {
+    return {
+      innerClasses: '',
+      fonticon: '',
+      containerClasses: '',
+      selected: false,
+      title: '',
+      disabled: false,
+      toggleCallback: null,
+      text: '',
+      iconDefaultClass: 'icon'
+    };
+  },
+
+  render: function () {
+    var iconClasses = this.props.iconDefaultClass + ' ' + this.props.fonticon + ' ' + this.props.innerClasses,
+        containerClasses = 'button ' + this.props.containerClasses;
+
+    if (this.props.selected) {
+      containerClasses = containerClasses + ' js-headerbar-togglebutton-selected';
     }
-  });
 
-  var ToggleHeaderButton = React.createClass({
-    getDefaultProps: function () {
-      return {
-        innerClasses: '',
-        fonticon: '',
-        containerClasses: '',
-        selected: false,
-        title: '',
-        disabled: false,
-        toggleCallback: null,
-        text: '',
-        iconDefaultClass: 'icon'
-      };
-    },
+    return (
+      <button
+        title={this.props.title}
+        disabled={this.props.disabled}
+        onClick={this.props.toggleCallback}
+        className={containerClasses}
+        >
+        <i className={iconClasses}></i><span>{this.props.text}</span>
+      </button>
+    );
+  }
+});
 
-    render: function () {
-      var iconClasses = this.props.iconDefaultClass + ' ' + this.props.fonticon + ' ' + this.props.innerClasses,
-          containerClasses = 'button ' + this.props.containerClasses;
 
-      if (this.props.selected) {
-        containerClasses = containerClasses + ' js-headerbar-togglebutton-selected';
-      }
+var BulkActionComponent = React.createClass({
+
+  propTypes: {
+    hasSelectedItem: React.PropTypes.bool.isRequired,
+    removeItem: React.PropTypes.func.isRequired,
+    selectAll: React.PropTypes.func,
+    toggleSelect: React.PropTypes.func.isRequired,
+    isChecked: React.PropTypes.bool.isRequired,
+    disabled: React.PropTypes.bool
+  },
+
+  getDefaultProps: function () {
+    return {
+      disabled: false,
+      title: 'Select rows that can be...',
+      bulkIcon: 'fonticon-trash',
+      buttonTitle: 'Delete all selected',
+      dropdownContentText: 'Deleted',
+      enableOverlay: false
+    };
+  },
+
+  render: function () {
+    return (
+      <div className="bulk-action-component">
+        <div className="bulk-action-component-selector-group">
+          {this.getMasterSelector()}
+          {this.getMultiSelectOptions()}
+        </div>
+      </div>
+    );
+  },
 
-      return (
-        <button
-          title={this.props.title}
-          disabled={this.props.disabled}
-          onClick={this.props.toggleCallback}
-          className={containerClasses}
-          >
-          <i className={iconClasses}></i><span>{this.props.text}</span>
-        </button>
-      );
+  getMultiSelectOptions: function () {
+    if (!this.props.hasSelectedItem) {
+      return null;
     }
-  });
-
 
-  var BulkActionComponent = React.createClass({
+    return (
+      <button
+        onClick={this.props.removeItem}
+        className={'fonticon ' + this.props.bulkIcon}
+        title={this.props.buttonTitle} />
+    );
+  },
 
-    propTypes: {
-      hasSelectedItem: React.PropTypes.bool.isRequired,
-      removeItem: React.PropTypes.func.isRequired,
-      selectAll: React.PropTypes.func,
-      toggleSelect: React.PropTypes.func.isRequired,
-      isChecked: React.PropTypes.bool.isRequired,
-      disabled: React.PropTypes.bool
-    },
+  getPopupContent: function () {
+    return (
+      <ul className="bulk-action-component-popover-actions">
+        <li onClick={this.selectAll} >
+          <i className="icon fonticon-cancel"></i> {this.props.dropdownContentText}
+        </li>
+      </ul>
+    );
+  },
 
-    getDefaultProps: function () {
-      return {
-        disabled: false,
-        title: 'Select rows that can be...',
-        bulkIcon: 'fonticon-trash',
-        buttonTitle: 'Delete all selected',
-        dropdownContentText: 'Deleted',
-        enableOverlay: false
-      };
-    },
+  selectAll: function () {
+    this.refs.bulkActionPopover.hide();
+    this.props.selectAll();
+  },
 
-    render: function () {
-      return (
-        <div className="bulk-action-component">
-          <div className="bulk-action-component-selector-group">
-            {this.getMasterSelector()}
-            {this.getMultiSelectOptions()}
-          </div>
+  getOverlay: function () {
+    return (
+      <OverlayTrigger
+        ref="bulkActionPopover"
+        trigger="click"
+        placement="bottom"
+        rootClose={true}
+        overlay={
+          <Popover id="bulk-action-component-popover" title={this.props.title}>
+            {this.getPopupContent()}
+          </Popover>
+        }>
+        <div className="arrow-button">
+          <i className="fonticon fonticon-play"></i>
         </div>
-      );
-    },
+      </OverlayTrigger>
+    );
+  },
 
-    getMultiSelectOptions: function () {
-      if (!this.props.hasSelectedItem) {
-        return null;
-      }
+  getMasterSelector: function () {
+    return (
+      <div className="bulk-action-component-panel">
+        <input type="checkbox"
+          checked={this.props.isChecked}
+          onChange={this.props.toggleSelect}
+          disabled={this.props.disabled} />
+        {this.props.enableOverlay ? <div className="separator"></div> : null}
+        {this.props.enableOverlay ? this.getOverlay() : null}
+      </div>
+    );
+  },
 
-      return (
-        <button
-          onClick={this.props.removeItem}
-          className={'fonticon ' + this.props.bulkIcon}
-          title={this.props.buttonTitle} />
-      );
-    },
+});
 
-    getPopupContent: function () {
-      return (
-        <ul className="bulk-action-component-popover-actions">
-          <li onClick={this.selectAll} >
-            <i className="icon fonticon-cancel"></i> {this.props.dropdownContentText}
-          </li>
-        </ul>
-      );
-    },
+var StyledSelect = React.createClass({
+  propTypes: {
+    selectValue: React.PropTypes.string.isRequired,
+    selectId: React.PropTypes.string.isRequired,
+    selectChange: React.PropTypes.func.isRequired
+  },
 
-    selectAll: function () {
-      this.refs.bulkActionPopover.hide();
-      this.props.selectAll();
-    },
+  render: function () {
+    return (
+      <div className="styled-select">
+        <label htmlFor={this.props.selectId}>
+          <i className="fonticon-down-dir"></i>
+          <select
+            value={this.props.selectValue}
+            id={this.props.selectId}
+            className={this.props.selectValue}
+            onChange={this.props.selectChange}
+          >
+            {this.props.selectContent}
+          </select>
+        </label>
+      </div>
+    );
+  }
+});
 
-    getOverlay: function () {
-      return (
-        <ReactBootstrap.OverlayTrigger
-          ref="bulkActionPopover"
-          trigger="click"
-          placement="bottom"
-          rootClose={true}
-          overlay={
-            <ReactBootstrap.Popover id="bulk-action-component-popover" title={this.props.title}>
-              {this.getPopupContent()}
-            </ReactBootstrap.Popover>
-          }>
-          <div className="arrow-button">
-            <i className="fonticon fonticon-play"></i>
-          </div>
-        </ReactBootstrap.OverlayTrigger>
-      );
-    },
 
-    getMasterSelector: function () {
-      return (
-        <div className="bulk-action-component-panel">
-          <input type="checkbox"
-            checked={this.props.isChecked}
-            onChange={this.props.toggleSelect}
-            disabled={this.props.disabled} />
-          {this.props.enableOverlay ? <div className="separator"></div> : null}
-          {this.props.enableOverlay ? this.getOverlay() : null}
-        </div>
-      );
-    },
+/**
+ * A pre-packaged JS editor panel for use on the Edit Index / Mango pages. Includes options for a title, zen mode
+ * icon and beautify button.
+ */
+var CodeEditorPanel = React.createClass({
+  getDefaultProps: function () {
+    return {
+      id: 'code-editor',
+      className: '',
+      defaultCode: '',
+      title: '',
+      docLink: '',
+      allowZenMode: true,
+      blur: function () {}
+    };
+  },
+
+  getInitialState: function () {
+    return this.getStoreState();
+  },
+
+  getStoreState: function () {
+    return {
+      zenModeEnabled: false,
+      code: this.props.defaultCode
+    };
+  },
+
+  componentWillReceiveProps: function (nextProps) {
+    if (nextProps.defaultCode !== this.props.defaultCode) {
+      this.setState({ code: nextProps.defaultCode });
+    }
+  },
 
-  });
+  // list of JSHINT errors to ignore: gets around problem of anonymous functions not being valid
+  ignorableErrors: [
+    'Missing name in function declaration.',
+    "['{a}'] is better written in dot notation."
+  ],
 
-  var StyledSelect = React.createClass({
-    propTypes: {
-      selectValue: React.PropTypes.string.isRequired,
-      selectId: React.PropTypes.string.isRequired,
-      selectChange: React.PropTypes.func.isRequired
-    },
+  getZenModeIcon: function () {
+    if (this.props.allowZenMode) {
+      return <span className="fonticon fonticon-resize-full zen-editor-icon" title="Enter Zen mode" onClick={this.enterZenMode}></span>;
+    }
+  },
 
-    render: function () {
+  getDocIcon: function () {
+    if (this.props.docLink) {
       return (
-        <div className="styled-select">
-          <label htmlFor={this.props.selectId}>
-            <i className="fonticon-down-dir"></i>
-            <select
-              value={this.props.selectValue}
-              id={this.props.selectId}
-              className={this.props.selectValue}
-              onChange={this.props.selectChange}
-            >
-              {this.props.selectContent}
-            </select>
-          </label>
-        </div>
+        <a className="help-link"
+          data-bypass="true"
+          href={this.props.docLink}
+          target="_blank"
+        >
+          <i className="icon-question-sign"></i>
+        </a>
       );
     }
-  });
-
-
-  /**
-   * A pre-packaged JS editor panel for use on the Edit Index / Mango pages. Includes options for a title, zen mode
-   * icon and beautify button.
-   */
-  var CodeEditorPanel = React.createClass({
-    getDefaultProps: function () {
-      return {
-        id: 'code-editor',
-        className: '',
-        defaultCode: '',
-        title: '',
-        docLink: '',
-        allowZenMode: true,
-        blur: function () {}
-      };
-    },
-
-    getInitialState: function () {
-      return this.getStoreState();
-    },
-
-    getStoreState: function () {
-      return {
-        zenModeEnabled: false,
-        code: this.props.defaultCode
-      };
-    },
-
-    componentWillReceiveProps: function (nextProps) {
-      if (nextProps.defaultCode !== this.props.defaultCode) {
-        this.setState({ code: nextProps.defaultCode });
-      }
-    },
-
-    // list of JSHINT errors to ignore: gets around problem of anonymous functions not being valid
-    ignorableErrors: [
-      'Missing name in function declaration.',
-      "['{a}'] is better written in dot notation."
-    ],
-
-    getZenModeIcon: function () {
-      if (this.props.allowZenMode) {
-        return <span className="fonticon fonticon-resize-full zen-editor-icon" title="Enter Zen mode" onClick={this.enterZenMode}></span>;
-      }
-    },
-
-    getDocIcon: function () {
-      if (this.props.docLink) {
-        return (
-          <a className="help-link"
-            data-bypass="true"
-            href={this.props.docLink}
-            target="_blank"
-          >
-            <i className="icon-question-sign"></i>
-          </a>
-        );
-      }
-    },
-
-    getZenModeOverlay: function () {
-      if (this.state.zenModeEnabled) {
-        return (
-          <ZenModeOverlay
-            defaultCode={this.state.code}
-            mode={this.props.mode}
-            ignorableErrors={this.ignorableErrors}
-            onExit={this.exitZenMode} />
-        );
-      }
-    },
-
-    enterZenMode: function () {
-      this.setState({
-        zenModeEnabled: true,
-        code: this.refs.codeEditor.getValue()
-      });
-    },
-
-    exitZenMode: function (content) {
-      this.setState({ zenModeEnabled: false });
-      this.getEditor().setValue(content);
-    },
-
-    getEditor: function () {
-      return this.refs.codeEditor;
-    },
-
-    getValue: function () {
-      return this.getEditor().getValue();
-    },
-
-    beautify: function (code) {
-      this.setState({ code: code });
-      this.getEditor().setValue(code);
-    },
+  },
 
-    update: function () {
-      this.getEditor().setValue(this.state.code);
-    },
-
-    render: function () {
-      var classes = 'control-group';
-      if (this.props.className) {
-        classes += ' ' + this.props.className;
-      }
+  getZenModeOverlay: function () {
+    if (this.state.zenModeEnabled) {
       return (
-        <div className={classes}>
-          <label>
-            <span>{this.props.title}</span>
-            {this.getDocIcon()}
-            {this.getZenModeIcon()}
-          </label>
-          <CodeEditor
-            id={this.props.id}
-            ref="codeEditor"
-            mode="javascript"
-            defaultCode={this.state.code}
-            showGutter={true}
-            ignorableErrors={this.ignorableErrors}
-            setHeightToLineCount={true}
-            blur={this.props.blur}
-          />
-          <Beautify code={this.state.code} beautifiedCode={this.beautify} />
-          {this.getZenModeOverlay()}
-        </div>
+        <ZenModeOverlay
+          defaultCode={this.state.code}
+          mode={this.props.mode}
+          ignorableErrors={this.ignorableErrors}
+          onExit={this.exitZenMode} />
       );
     }
-  });
-
-
-  require('brace/mode/javascript');
-  require('brace/mode/json');
-  require('brace/theme/idle_fingers');
-
-  var CodeEditor = React.createClass({
-    getDefaultProps: function () {
-      return {
-        id: 'code-editor',
-        mode: 'javascript',
-        theme: 'idle_fingers',
-        fontSize: 13,
-
-        // this sets the default value for the editor. On the fly changes are stored in state in this component only. To
-        // change the editor content after initial construction use CodeEditor.setValue()
-        defaultCode: '',
-
-        showGutter: true,
-        highlightActiveLine: true,
-        showPrintMargin: false,
-        autoScrollEditorIntoView: true,
-        autoFocus: false,
-        stringEditModalEnabled: false,
-
-        // these two options create auto-resizeable code editors, with a maximum number of lines
-        setHeightToLineCount: false,
-        maxLines: 10,
-
-        // optional editor key commands (e.g. specific save action)
-        editorCommands: [],
-
-        // notifies users that there is unsaved changes in the editor when navigating away from the page
-        notifyUnsavedChanges: false,
-
-        // an optional array of ignorable Ace errors. Lets us filter out errors based on context
-        ignorableErrors: [],
-
-        // un-Reacty, but the code editor is a self-contained component and it's helpful to be able to tie into
-        // editor specific events like content changes and leaving the editor
-        change: function () {},
-        blur: function () {}
-      };
-    },
-
-    getInitialState: function () {
-      return {
-        originalCode: this.props.defaultCode,
-
-        // these are all related to the (optional) string edit modal
-        stringEditModalVisible: false,
-        stringEditIconVisible: false,
-        stringEditIconStyle: {},
-        stringEditModalValue: ''
-      };
-    },
-
-    hasChanged: function () {
-      return !_.isEqual(this.state.originalCode, this.getValue());
-    },
-
-    clearChanges: function () {
-      this.setState({
-        originalCode: this.getValue()
-      });
-    },
-
-    setupAce: function (props, shouldUpdateCode) {
-      this.editor = ace.edit(ReactDOM.findDOMNode(this.refs.ace));
-
-      // suppresses an Ace editor error
-      this.editor.$blockScrolling = Infinity;
-
-      if (shouldUpdateCode) {
-        this.setValue(props.defaultCode);
-      }
-
-      this.editor.setShowPrintMargin(props.showPrintMargin);
-      this.editor.autoScrollEditorIntoView = props.autoScrollEditorIntoView;
-
-      this.editor.setOption('highlightActiveLine', this.props.highlightActiveLine);
-
-      if (this.props.setHeightToLineCount) {
-        this.setHeightToLineCount();
-      }
+  },
 
-      if (this.props.ignorableErrors) {
-        this.removeIgnorableAnnotations();
-      }
-
-      this.addCommands();
-      this.editor.getSession().setMode('ace/mode/' + props.mode);
-      this.editor.setTheme('ace/theme/' + props.theme);
-      this.editor.setFontSize(props.fontSize);
-      this.editor.getSession().setTabSize(2);
-      this.editor.getSession().setUseSoftTabs(true);
+  enterZenMode: function () {
+    this.setState({
+      zenModeEnabled: true,
+      code: this.refs.codeEditor.getValue()
+    });
+  },
+
+  exitZenMode: function (content) {
+    this.setState({ zenModeEnabled: false });
+    this.getEditor().setValue(content);
+  },
+
+  getEditor: function () {
+    return this.refs.codeEditor;
+  },
+
+  getValue: function () {
+    return this.getEditor().getValue();
+  },
+
+  beautify: function (code) {
+    this.setState({ code: code });
+    this.getEditor().setValue(code);
+  },
+
+  update: function () {
+    this.getEditor().setValue(this.state.code);
+  },
+
+  render: function () {
+    var classes = 'control-group';
+    if (this.props.className) {
+      classes += ' ' + this.props.className;
+    }
+    return (
+      <div className={classes}>
+        <label>
+          <span>{this.props.title}</span>
+          {this.getDocIcon()}
+          {this.getZenModeIcon()}
+        </label>
+        <CodeEditor
+          id={this.props.id}
+          ref="codeEditor"
+          mode="javascript"
+          defaultCode={this.state.code}
+          showGutter={true}
+          ignorableErrors={this.ignorableErrors}
+          setHeightToLineCount={true}
+          blur={this.props.blur}
+        />
+        <Beautify code={this.state.code} beautifiedCode={this.beautify} />
+        {this.getZenModeOverlay()}
+      </div>
+    );
+  }
+});
 
-      if (this.props.autoFocus) {
-        this.editor.focus();
-      }
-    },
 
-    addCommands: function () {
-      _.each(this.props.editorCommands, function (command) {
-        this.editor.commands.addCommand(command);
-      }, this);
-    },
+require('brace/mode/javascript');
+require('brace/mode/json');
+require('brace/theme/idle_fingers');
+
+var CodeEditor = React.createClass({
+  getDefaultProps: function () {
+    return {
+      id: 'code-editor',
+      mode: 'javascript',
+      theme: 'idle_fingers',
+      fontSize: 13,
+
+      // this sets the default value for the editor. On the fly changes are stored in state in this component only. To
+      // change the editor content after initial construction use CodeEditor.setValue()
+      defaultCode: '',
+
+      showGutter: true,
+      highlightActiveLine: true,
+      showPrintMargin: false,
+      autoScrollEditorIntoView: true,
+      autoFocus: false,
+      stringEditModalEnabled: false,
+
+      // these two options create auto-resizeable code editors, with a maximum number of lines
+      setHeightToLineCount: false,
+      maxLines: 10,
+
+      // optional editor key commands (e.g. specific save action)
+      editorCommands: [],
+
+      // notifies users that there is unsaved changes in the editor when navigating away from the page
+      notifyUnsavedChanges: false,
+
+      // an optional array of ignorable Ace errors. Lets us filter out errors based on context
+      ignorableErrors: [],
+
+      // un-Reacty, but the code editor is a self-contained component and it's helpful to be able to tie into
+      // editor specific events like content changes and leaving the editor
+      change: function () {},
+      blur: function () {}
+    };
+  },
+
+  getInitialState: function () {
+    return {
+      originalCode: this.props.defaultCode,
+
+      // these are all related to the (optional) string edit modal
+      stringEditModalVisible: false,
+      stringEditIconVisible: false,
+      stringEditIconStyle: {},
+      stringEditModalValue: ''
+    };
+  },
+
+  hasChanged: function () {
+    return !_.isEqual(this.state.originalCode, this.getValue());
+  },
+
+  clearChanges: function () {
+    this.setState({
+      originalCode: this.getValue()
+    });
+  },
 
-    setupEvents: function () {
-      this.editor.on('blur', _.bind(this.onBlur, this));
-      this.editor.on('change', _.bind(this.onContentChange, this));
+  setupAce: function (props, shouldUpdateCode) {
+    this.editor = ace.edit(ReactDOM.findDOMNode(this.refs.ace));
 
-      if (this.props.stringEditModalEnabled) {
-        this.editor.on('changeSelection', _.bind(this.showHideEditStringGutterIcon, this));
-        this.editor.getSession().on('changeBackMarker', _.bind(this.showHideEditStringGutterIcon, this));
-        this.editor.getSession().on('changeScrollTop', _.bind(this.updateEditStringGutterIconPosition, this));
-      }
+    // suppresses an Ace editor error
+    this.editor.$blockScrolling = Infinity;
 
-      if (this.props.notifyUnsavedChanges) {
-        $(window).on('beforeunload.editor_' + this.props.id, _.bind(this.quitWarningMsg));
-        FauxtonAPI.beforeUnload('editor_' + this.props.id, _.bind(this.quitWarningMsg, this));
-      }
-    },
+    if (shouldUpdateCode) {
+      this.setValue(props.defaultCode);
+    }
 
-    onBlur: function () {
-      this.props.blur(this.getValue());
-    },
+    this.editor.setShowPrintMargin(props.showPrintMargin);
+    this.editor.autoScrollEditorIntoView = props.autoScrollEditorIntoView;
 
-    onContentChange: function () {
-      if (this.props.setHeightToLineCount) {
-        this.setHeightToLineCount();
-      }
-      this.props.change(this.getValue());
-    },
+    this.editor.setOption('highlightActiveLine', this.props.highlightActiveLine);
 
-    quitWarningMsg: function () {
-      if (this.hasChanged()) {
-        return 'Your changes have not been saved. Click Cancel to return to the document, or OK to proceed.';
-      }
-    },
+    if (this.props.setHeightToLineCount) {
+      this.setHeightToLineCount();
+    }
 
-    removeEvents: function () {
-      if (this.props.notifyUnsavedChanges) {
-        $(window).off('beforeunload.editor_' + this.props.id);
-        FauxtonAPI.removeBeforeUnload('editor_' + this.props.id);
-      }
-    },
+    if (this.props.ignorableErrors) {
+      this.removeIgnorableAnnotations();
+    }
 
-    setHeightToLineCount: function () {
-      var numLines = this.editor.getSession().getDocument().getLength();
-      var maxLines = (numLines > this.props.maxLines) ? this.props.maxLines : numLines;
-      this.editor.setOptions({
-        maxLines: maxLines
-      });
-    },
+    this.addCommands();
+    this.editor.getSession().setMode('ace/mode/' + props.mode);
+    this.editor.setTheme('ace/theme/' + props.theme);
+    this.editor.setFontSize(props.fontSize);
+    this.editor.getSession().setTabSize(2);
+    this.editor.getSession().setUseSoftTabs(true);
 
-    componentDidMount: function () {
-      this.setupAce(this.props, true);
-      this.setupEvents();
+    if (this.props.autoFocus) {
+      this.editor.focus();
+    }
+  },
+
+  addCommands: function () {
+    _.each(this.props.editorCommands, function (command) {
+      this.editor.commands.addCommand(command);
+    }, this);
+  },
+
+  setupEvents: function () {
+    this.editor.on('blur', _.bind(this.onBlur, this));
+    this.editor.on('change', _.bind(this.onContentChange, this));
+
+    if (this.props.stringEditModalEnabled) {
+      this.editor.on('changeSelection', _.bind(this.showHideEditStringGutterIcon, this));
+      this.editor.getSession().on('changeBackMarker', _.bind(this.showHideEditStringGutterIcon, this));
+      this.editor.getSession().on('changeScrollTop', _.bind(this.updateEditStringGutterIconPosition, this));
+    }
 
-      if (this.props.autoFocus) {
-        this.editor.focus();
-      }
-    },
+    if (this.props.notifyUnsavedChanges) {
+      $(window).on('beforeunload.editor_' + this.props.id, _.bind(this.quitWarningMsg));
+      FauxtonAPI.beforeUnload('editor_' + this.props.id, _.bind(this.quitWarningMsg, this));
+    }
+  },
 
-    componentWillUnmount: function () {
-      this.removeEvents();
-      this.editor.destroy();
-    },
+  onBlur: function () {
+    this.props.blur(this.getValue());
+  },
 
-    componentWillReceiveProps: function (nextProps) {
-      this.setupAce(nextProps, false);
-    },
+  onContentChange: function () {
+    if (this.props.setHeightToLineCount) {
+      this.setHeightToLineCount();
+    }
+    this.props.change(this.getValue());
+  },
 
-    getAnnotations: function () {
-      return this.editor.getSession().getAnnotations();
-    },
+  quitWarningMsg: function () {
+    if (this.hasChanged()) {
+      return 'Your changes have not been saved. Click Cancel to return to the document, or OK to proceed.';
+    }
+  },
 
-    isIgnorableError: function (msg) {
-      return _.contains(this.props.ignorableErrors, msg);
-    },
+  removeEvents: function () {
+    if (this.props.notifyUnsavedChanges) {
+      $(window).off('beforeunload.editor_' + this.props.id);
+      FauxtonAPI.removeBeforeUnload('editor_' + this.props.id);
+    }
+  },
 
-    removeIgnorableAnnotations: function () {
-      var isIgnorableError = this.isIgnorableError;
-      this.editor.getSession().on('changeAnnotation', function () {
-        var annotations = this.editor.getSession().getAnnotations();
-        var newAnnotations = _.reduce(annotations, function (annotations, error) {
-          if (!isIgnorableError(error.raw)) {
-            annotations.push(error);
-          }
-          return annotations;
-        }, []);
-
-        if (annotations.length !== newAnnotations.length) {
-          this.editor.getSession().setAnnotations(newAnnotations);
-        }
-      }.bind(this));
-    },
+  setHeightToLineCount: function () {
+    var numLines = this.editor.getSession().getDocument().getLength();
+    var maxLines = (numLines > this.props.maxLines) ? this.props.maxLines : numLines;
+    this.editor.setOptions({
+      maxLines: maxLines
+    });
+  },
 
-    showHideEditStringGutterIcon: function (e) {
-      if (this.hasErrors() || !this.parseLineForStringMatch()) {
-        this.setState({ stringEditIconVisible: false });
-        return false;
-      }
+  componentDidMount: function () {
+    this.setupAce(this.props, true);
+    this.setupEvents();
 
-      this.setState({
-        stringEditIconVisible: true,
-        stringEditIconStyle: {
-          top: this.getGutterIconPosition()
+    if (this.props.autoFocus) {
+      this.editor.focus();
+    }
+  },
+
+  componentWillUnmount: function () {
+    this.removeEvents();
+    this.editor.destroy();
+  },
+
+  componentWillReceiveProps: function (nextProps) {
+    this.setupAce(nextProps, false);
+  },
+
+  getAnnotations: function () {
+    return this.editor.getSession().getAnnotations();
+  },
+
+  isIgnorableError: function (msg) {
+    return _.contains(this.props.ignorableErrors, msg);
+  },
+
+  removeIgnorableAnnotations: function () {
+    var isIgnorableError = this.isIgnorableError;
+    this.editor.getSession().on('changeAnnotation', function () {
+      var annotations = this.editor.getSession().getAnnotations();
+      var newAnnotations = _.reduce(annotations, function (annotations, error) {
+        if (!isIgnorableError(error.raw)) {
+          annotations.push(error);
         }
-      });
-
-      return true;
-    },
+        return annotations;
+      }, []);
 
-    updateEditStringGutterIconPosition: function () {
-      if (!this.state.stringEditIconVisible) {
-        return;
+      if (annotations.length !== newAnnotations.length) {
+        this.editor.getSession().setAnnotations(newAnnotations);
       }
-      this.setState({
-        stringEditIconStyle: {
-          top: this.getGutterIconPosition()
-        }
-      });
-    },
+    }.bind(this));
+  },
 
-    getGutterIconPosition: function () {
-      var rowHeight = this.getRowHeight();
-      var scrollTop = this.editor.session.getScrollTop();
-      var positionFromTop = (rowHeight * this.documentToScreenRow(this.getSelectionStart().row)) - scrollTop;
-      return positionFromTop + 'px';
-    },
-
-    parseLineForStringMatch: function () {
-      var selStart = this.getSelectionStart().row;
-      var selEnd   = this.getSelectionEnd().row;
-
-      // one JS(ON) string can't span more than one line - we edit one string, so ensure we don't select several lines
-      if (selStart >= 0 && selEnd >= 0 && selStart === selEnd && this.isRowExpanded(selStart)) {
-        var editLine = this.getLine(selStart),
-            editMatch = editLine.match(/^([ \t]*)("[a-zA-Z0-9_]*["|']: )?(["|'].*",?[ \t]*)$/);
-
-        if (editMatch) {
-          return editMatch;
-        }
-      }
+  showHideEditStringGutterIcon: function (e) {
+    if (this.hasErrors() || !this.parseLineForStringMatch()) {
+      this.setState({ stringEditIconVisible: false });
       return false;
-    },
-
-    openStringEditModal: function () {
-      var matches = this.parseLineForStringMatch();
-      var string = matches[3];
-      var lastChar = string.length - 1;
-      if (string.substring(string.length - 1) === ',') {
-        lastChar = string.length - 2;
-      }
-      string = string.substring(1, lastChar);
-
-      this.setState({
-        stringEditModalVisible: true,
-        stringEditModalValue: string
-      });
-    },
-
-    saveStringEditModal: function (newString) {
-      // replace the string on the selected line
-      var line = this.parseLineForStringMatch();
-      var indent = line[1] || '',
-          key = line[2] || '',
-          originalString = line[3],
-          comma = '';
-      if (originalString.substring(originalString.length - 1) === ',') {
-        comma = ',';
-      }
-      this.replaceCurrentLine(indent + key + JSON.stringify(newString) + comma + '\n');
-      this.closeStringEditModal();
-    },
-
-    closeStringEditModal: function () {
-      this.setState({
-        stringEditModalVisible: false
-      });
-    },
-
-    hasErrors: function () {
-      return !_.every(this.getAnnotations(), function (error) {
-        return this.isIgnorableError(error.raw);
-      }, this);
-    },
-
-    setReadOnly: function (readonly) {
-      this.editor.setReadOnly(readonly);
-    },
-
-    setValue: function (code, lineNumber) {
-      lineNumber = lineNumber ? lineNumber : -1;
-      this.editor.setValue(code, lineNumber);
-    },
-
-    getValue: function () {
-      return this.editor.getValue();
-    },
-
-    getEditor: function () {
-      return this;
-    },
-
-    getLine: function (lineNum) {
-      return this.editor.session.getLine(lineNum);
-    },
-
-    getSelectionStart: function () {
-      return this.editor.getSelectionRange().start;
-    },
-
-    getSelectionEnd: function () {
-      return this.editor.getSelectionRange().end;
-    },
-
-    getRowHeight: function () {
-      return this.editor.renderer.layerConfig.lineHeight;
-    },
-
-    isRowExpanded: function (row) {
-      return !this.editor.getSession().isRowFolded(row);
-    },
-
-    documentToScreenRow: function (row) {
-      return this.editor.getSession().documentToScreenRow(row, 0);
-    },
-
-    replaceCurrentLine: function (replacement) {
-      this.editor.getSelection().selectLine();
-      this.editor.insert(replacement);
-      this.editor.getSelection().moveCursorUp();
-    },
-
-    render: function () {
-      return (
-        <div>
-          <div ref="ace" className="js-editor" id={this.props.id}></div>
-          <button ref="stringEditIcon" className="btn string-edit" title="Edit string" disabled={!this.state.stringEditIconVisible}
-            style={this.state.stringEditIconStyle} onClick={this.openStringEditModal}>
-            <i className="icon icon-edit"></i>
-          </button>
-          <StringEditModal
-            ref="stringEditModal"
-            visible={this.state.stringEditModalVisible}
-            value={this.state.stringEditModalValue}
-            onSave={this.saveStringEditModal}
-            onClose={this.closeStringEditModal} />
-        </div>
-      );
     }
-  });
-
-
-  // this appears when the cursor is over a string. It shows an icon in the gutter that opens the modal.
-  var StringEditModal = React.createClass({
-
-    propTypes: {
-      value: React.PropTypes.string.isRequired,
-      visible: React.PropTypes.bool.isRequired,
-      onClose: React.PropTypes.func.isRequired,
-      onSave: React.PropTypes.func.isRequired
-    },
-
-    getDefaultProps: function () {
-      return {
-        visible: false,
-        onClose: function () { },
-        onSave: function () { }
-      };
-    },
 
-    componentDidMount: function () {
-      if (!this.props.visible) {
-        return;
-      }
-      this.initEditor(this.props.value);
-    },
-
-    componentDidUpdate: function (prevProps) {
-      if (!this.props.visible) {
-        return;
+    this.setState({
+      stringEditIconVisible: true,
+      stringEditIconStyle: {
+        top: this.getGutterIconPosition()
       }
-      var val = '';
-      if (!prevProps.visible && this.props.visible) {
-        val = Helpers.parseJSON(this.props.value);
-      }
-
-      this.initEditor(val);
-    },
-
-    initEditor: function (val) {
-      this.editor = ace.edit(ReactDOM.findDOMNode(this.refs.stringEditor));
-      this.editor.$blockScrolling = Infinity; // suppresses an Ace editor error
-      this.editor.setShowPrintMargin(false);
-      this.editor.setOption('highlightActiveLine', true);
-      this.editor.setTheme('ace/theme/idle_fingers');
-      this.editor.setValue(val, -1);
-    },
-
-    closeModal: function () {
-      this.props.onClose();
-    },
-
-    save: function () {
-      this.props.onSave(this.editor.getValue());
-    },
-
-    render: function () {
-      return (
-        <Modal dialogClassName="string-editor-modal" show={this.props.visible} onHide={this.closeModal}>
-          <Modal.Header closeButton={true}>
-            <Modal.Title>Edit text <span id="string-edit-header"></span></Modal.Title>
-          </Modal.Header>
-          <Modal.Body>
-            <div id="modal-error" className="hide alert alert-error"/>
-            <div id="string-editor-wrapper"><div ref="stringEditor" className="doc-code"></div></div>
-          </Modal.Body>
-          <Modal.Footer>
-            <button className="cancel-button btn" onClick={this.closeModal}><i className="icon fonticon-circle-x"></i> Cancel</button>
-            <button id="string-edit-save-btn" onClick={this.save} className="btn btn-success save">
-              <i className="fonticon-circle-check"></i> Save
-            </button>
-          </Modal.Footer>
-        </Modal>
-      );
-    }
-  });
-
-
-  // Zen mode editing has very few options:
-  // - It covers the full screen, hiding everything else
-  // - Two themes: light & dark (choice stored in local storage)
-  // - No save option, but has a 1-1 map with a <CodeEditor /> element which gets updated when the user leaves
-  // - [Escape] closes the mode, as does clicking the shrink icon at the top right
-  var ZenModeOverlay = React.createClass({
-    getDefaultProps: function () {
-      return {
-        mode: 'javascript',
-        defaultCode: '',
-        ignorableErrors: [],
-        onExit: null,
-        highlightActiveLine: false
-      };
-    },
-
-    themes: {
-      dark: 'idle_fingers',
-      light: 'dawn'
-    },
-
-    getInitialState: function () {
-      return this.getStoreState();
-    },
-
-    getStoreState: function () {
-      return {
-        theme: this.getZenTheme(),
-        code: this.props.defaultCode
-      };
-    },
-
-    getZenTheme: function () {
-      var selectedTheme = app.utils.localStorageGet('zenTheme');
-      return _.isUndefined(selectedTheme) ? 'dark' : selectedTheme;
-    },
-
-    onChange: function () {
-      this.setState(this.getStoreState());
-    },
-
-    componentDidMount: function () {
-      $(ReactDOM.findDOMNode(this.refs.exit)).tooltip({ placement: 'left' });
-      $(ReactDOM.findDOMNode(this.refs.theme)).tooltip({ placement: 'left' });
-    },
-
-    exitZenMode: function () {
-      this.props.onExit(this.getValue());
-    },
-
-    getValue: function () {
-      return this.refs.ace.getValue();
-    },
-
-    toggleTheme: function () {
-      var newTheme = (this.state.theme === 'dark') ? 'light' : 'dark';
-      this.setState({
-        theme: newTheme,
-        code: this.getValue()
-      });
-      app.utils.localStorageSet('zenTheme', newTheme);
-    },
-
-    setValue: function (code, lineNumber) {
-      lineNumber = lineNumber ? lineNumber : -1;
-      this.editor.setValue(code, lineNumber);
-    },
-
-    render: function () {
-      var classes = 'full-page-editor-modal-wrapper zen-theme-' + this.state.theme;
+    });
 
-      var editorCommands = [{
-        name: 'close',
-        bindKey: { win: 'ESC', mac: 'ESC' },
-        exec: this.exitZenMode
-      }];
+    return true;
+  },
 
-      return (
-        <div className={classes}>
-          <div className="zen-mode-controls">
-            <ul>
-              <li>
-                <span ref="exit"
-                  className="fonticon fonticon-resize-small js-exit-zen-mode"
-                  data-toggle="tooltip"
-                  data-container=".zen-mode-controls .tooltips"
-                  title="Exit zen mode (`esc`)"
-                  onClick={this.exitZenMode}></span>
-              </li>
-              <li>
-                <span ref="theme"
-                  className="fonticon fonticon-picture js-toggle-theme"
-                  data-toggle="tooltip"
-                  data-container=".zen-mode-controls .tooltips"
-                  title="Switch zen theme"
-                  onClick={this.toggleTheme}></span>
-              </li>
-            </ul>
-            <div className="tooltips"></div>
-          </div>
-          <CodeEditor
-            ref="ace"
-            autoFocus={true}
-            theme={this.themes[this.state.theme]}
-            defaultCode={this.props.defaultCode}
-            editorCommands={editorCommands}
-            ignorableErrors={this.props.ignorableErrors}
-            highlightActiveLine={this.props.highlightActiveLine}
-          />
-        </div>
-      );
+  updateEditStringGutterIconPosition: function () {
+    if (!this.state.stringEditIconVisible) {
+      return;
     }
-  });
-
-
-  var Beautify = React.createClass({
-    noOfLines: function () {
-      return this.props.code.split(/\r\n|\r|\n/).length;
-    },
-
-    canBeautify: function () {
-      return this.noOfLines() === 1;
-    },
-
-    addTooltip: function () {
-      if (this.canBeautify) {
-        $('.beautify-tooltip').tooltip({ placement: 'right' });
+    this.setState({
+      stringEditIconStyle: {
+        top: this.getGutterIconPosition()
       }
-    },
-
-    componentDidMount: function () {
-      this.addTooltip();
-    },
-
-    beautify: function (event) {
-      event.preventDefault();
-      var beautifiedCode = beautifyHelper(this.props.code);
-      this.props.beautifiedCode(beautifiedCode);
-      $('.beautify-tooltip').tooltip('hide');
-    },
-
-    render: function () {
-      if (!this.canBeautify()) {
-        return null;
+    });
+  },
+
+  getGutterIconPosition: function () {
+    var rowHeight = this.getRowHeight();
+    var scrollTop = this.editor.session.getScrollTop();
+    var positionFromTop = (rowHeight * this.documentToScreenRow(this.getSelectionStart().row)) - scrollTop;
+    return positionFromTop + 'px';
+  },
+
+  parseLineForStringMatch: function () {
+    var selStart = this.getSelectionStart().row;
+    var selEnd   = this.getSelectionEnd().row;
+
+    // one JS(ON) string can't span more than one line - we edit one string, so ensure we don't select several lines
+    if (selStart >= 0 && selEnd >= 0 && selStart === selEnd && this.isRowExpanded(selStart)) {
+      var editLine = this.getLine(selStart),
+          editMatch = editLine.match(/^([ \t]*)("[a-zA-Z0-9_]*["|']: )?(["|'].*",?[ \t]*)$/);
+
+      if (editMatch) {
+        return editMatch;
       }
-
-      return (
-        <button
-          onClick={this.beautify}
-          className="beautify beautify_map btn btn-primary btn-small beautify-tooltip"
-          type="button"
-          data-toggle="tooltip"
-          title="Reformat your minified code to make edits to it."
-        >
-          beautify this code
-        </button>
-      );
     }
-  });
-
-  var PaddedBorderedBox = React.createClass({
-    render: function () {
-      return (
-        <div className="bordered-box">
-          <div className="padded-box">
-            {this.props.children}
-          </div>
-        </div>
-      );
+    return false;
+  },
+
+  openStringEditModal: function () {
+    var matches = this.parseLineForStringMatch();
+    var string = matches[3];
+    var lastChar = string.length - 1;
+    if (string.substring(string.length - 1) === ',') {
+      lastChar = string.length - 2;
     }
-  });
-
-  var Document = React.createClass({
-    propTypes: {
-      docIdentifier: React.PropTypes.string.isRequired,
-      docChecked: React.PropTypes.func.isRequired,
-      truncate: React.PropTypes.bool,
-      maxRows: React.PropTypes.number
-    },
-
-    getDefaultProps: function () {
-      return {
-        truncate: true,
-        maxRows: 500
-      };
-    },
-
-    onChange: function (e) {
-      e.preventDefault();
-      this.props.docChecked(this.props.doc.id, this.props.doc._rev);
-    },
+    string = string.substring(1, lastChar);
 
-    getUrlFragment: function () {
-      if (!this.props.children) {
-        return '';
-      }
-
-      return (
-        <div className="doc-edit-symbol pull-right" title="Edit document">
-          {this.props.children}
-        </div>
-      );
-    },
-
-    getExtensionIcons: function () {
-      var extensions = FauxtonAPI.getExtensions('DocList:icons');
-      return _.map(extensions, function (Extension, i) {
-        return (<Extension doc={this.props.doc} key={i} />);
-      }, this);
-    },
-
-    getCheckbox: function () {
-      if (!this.props.isDeletable) {
-        return <div className="checkbox-dummy"></div>;
-      }
-
-      return (
-        <div className="checkbox inline">
-          <input
-            id={'checkbox-' + this.props.docIdentifier}
-            checked={this.props.checked}
-            data-checked={this.props.checked}
-            type="checkbox"
-            onChange={this.onChange}
-            className="js-row-select" />
-          <label onClick={this.onChange}
-            className="label-checkbox-doclist"
-            htmlFor={'checkbox-' + this.props.docIdentifier} />
-        </div>
-      );
-    },
+    this.setState({
+      stringEditModalVisible: true,
+      stringEditModalValue: string
+    });
+  },
+
+  saveStringEditModal: function (newString) {
+    // replace the string on the selected line
+    var line = this.parseLineForStringMatch();
+    var indent = line[1] || '',
+        key = line[2] || '',
+        originalString = line[3],
+        comma = '';
+    if (originalString.substring(originalString.length - 1) === ',') {
+      comma = ',';
+    }
+    this.replaceCurrentLine(indent + key + JSON.stringify(newString) + comma + '\n');
+    this.closeStringEditModal();
+  },
 
-    onDoubleClick: function (e) {
-      this.props.onDoubleClick(this.props.docIdentifier, this.props.doc, e);
-    },
+  closeStringEditModal: function () {
+    this.setState({
+      stringEditModalVisible: false
+    });
+  },
+
+  hasErrors: function () {
+    return !_.every(this.getAnnotations(), function (error) {
+      return this.isIgnorableError(error.raw);
+    }, this);
+  },
+
+  setReadOnly: function (readonly) {
+    this.editor.setReadOnly(readonly);
+  },
+
+  setValue: function (code, lineNumber) {
+    lineNumber = lineNumber ? lineNumber : -1;
+    this.editor.setValue(code, lineNumber);
+  },
+
+  getValue: function () {
+    return this.editor.getValue();
+  },
+
+  getEditor: function () {
+    return this;
+  },
+
+  getLine: function (lineNum) {
+    return this.editor.session.getLine(lineNum);
+  },
+
+  getSelectionStart: function () {
+    return this.editor.getSelectionRange().start;
+  },
+
+  getSelectionEnd: function () {
+    return this.editor.getSelectionRange().end;
+  },
+
+  getRowHeight: function () {
+    return this.editor.renderer.layerConfig.lineHeight;
+  },
+
+  isRowExpanded: function (row) {
+    return !this.editor.getSession().isRowFolded(row);
+  },
+
+  documentToScreenRow: function (row) {
+    return this.editor.getSession().documentToScreenRow(row, 0);
+  },
+
+  replaceCurrentLine: function (replacement) {
+    this.editor.getSelection().selectLine();
+    this.editor.insert(replacement);
+    this.editor.getSelection().moveCursorUp();
+  },
+
+  render: function () {
+    return (
+      <div>
+        <div ref="ace" className="js-editor" id={this.props.id}></div>
+        <button ref="stringEditIcon" className="btn string-edit" title="Edit string" disabled={!this.state.stringEditIconVisible}
+          style={this.state.stringEditIconStyle} onClick={this.openStringEditModal}>
+          <i className="icon icon-edit"></i>
+        </button>
+        <StringEditModal
+          ref="stringEditModal"
+          visible={this.state.stringEditModalVisible}
+          value={this.state.stringEditModalValue}
+          onSave={this.saveStringEditModal}
+          onClose={this.closeStringEditModal} />
+      </div>
+    );
+  }
+});
 
-    getDocContent: function () {
-      if (_.isEmpty(this.props.docContent)) {
-        return null;
-      }
 
-      // if need be, truncate the document
-      var content = this.props.docContent;
-      var isTruncated = false;
-      if (this.props.truncate) {
-        var result = Helpers.truncateDoc(this.props.docContent, this.props.maxRows);
-        isTruncated = result.isTruncated;
-        content = result.content;
-      }
+// this appears when the cursor is over a string. It shows an icon in the gutter that opens the modal.
+var StringEditModal = React.createClass({
 
-      return (
-        <div className="doc-data">
-          <pre className="prettyprint">{content}</pre>
-          {isTruncated ? <div className="doc-content-truncated">(truncated)</div> : null}
-        </div>
-      );
-    },
+  propTypes: {
+    value: React.PropTypes.string.isRequired,
+    visible: React.PropTypes.bool.isRequired,
+    onClose: React.PropTypes.func.isRequired,
+    onSave: React.PropTypes.func.isRequired
+  },
 
-    render: function () {
-      return (
-        <div data-id={this.props.docIdentifier} onDoubleClick={this.onDoubleClick} className="doc-row">
-          <div className="custom-inputs">
-            {this.getCheckbox()}
-          </div>
-          <div className="doc-item">
-            <header>
-              <span className="header-keylabel">
-                {this.props.keylabel}
-              </span>
-              <span className="header-doc-id">
-                {this.props.header ? '"' + this.props.header + '"' : null}
-              </span>
-              {this.getUrlFragment()}
-              <div className="doc-item-extension-icons pull-right">{this.getExtensionIcons()}</div>
-            </header>
-            {this.getDocContent()}
-          </div>
-          <div className="clearfix"></div>
-        </div>
-      );
-    }
-  });
+  getDefaultProps: function () {
+    return {
+      visible: false,
+      onClose: function () { },
+      onSave: function () { }
+    };
+  },
 
-  var LoadLines = React.createClass({
-    render: function () {
-      return (
-        <div className="loading-lines">
-          <div id="line1"> </div>
-          <div id="line2"> </div>
-          <div id="line3"> </div>
-          <div id="line4"> </div>
-        </div>
-      );
+  componentDidMount: function () {
+    if (!this.props.visible) {
+      return;
     }
-  });
-
-  var ConfirmButton = React.createClass({
-    propTypes: {
-      showIcon: React.PropTypes.bool,
-      id: React.PropTypes.string,
-      customIcon: React.PropTypes.string,
-      style: React.PropTypes.object,
-      buttonType: React.PropTypes.string,
-      'data-id': React.PropTypes.string,
-      onClick: React.PropTypes.func
-    },
-
-    getDefaultProps: function () {
-      return {
-        showIcon: true,
-        customIcon: 'fonticon-ok-circled',
-        buttonType: 'btn-success',
-        style: {},
-        'data-id': null,
-        onClick: function () { }
-      };
-    },
-
-    getIcon: function () {
-      if (!this.props.showIcon) {
-        return null;
-      }
-      return (
-        <i className={"icon " + this.props.customIcon} />
-      );
-    },
+    this.initEditor(this.props.value);
+  },
 
-    render: function () {
-      const { onClick, buttonType, id, style, text } = this.props;
-      return (
-        <button
-          onClick={onClick}
-          type="submit"
-          data-id={this.props['data-id']}
-          className={'btn save ' + buttonType}
-          id={id}
-          style={style}
-        >
-          {this.getIcon()}
-          {text}
-        </button>
-      );
+  componentDidUpdate: function (prevProps) {
+    if (!this.props.visible) {
+      return;
+    }
+    var val = '';
+    if (!prevProps.visible && this.props.visible) {
+      val = Helpers.parseJSON(this.props.value);
     }
-  });
 
-  var MenuDropDown = React.createClass({
+    this.initEditor(val);
+  },
 
-    getDefaultProps: function () {
-      return {
-        icon: 'fonticon-plus-circled'
-      };
-    },
+  initEditor: function (val) {
+    this.editor = ace.edit(ReactDOM.findDOMNode(this.refs.stringEditor));
+    this.editor.$blockScrolling = Infinity; // suppresses an Ace editor error
+    this.editor.setShowPrintMargin(false);
+    this.editor.setOption('highlightActiveLine', true);
+    this.editor.setTheme('ace/theme/idle_fingers');
+    this.editor.setValue(val, -1);
+  },
 
-    createSectionLinks: function (links) {
-      if (!links) { return null; }
+  closeModal: function () {
+    this.props.onClose();
+  },
 
-      return links.map(function (link, key) {
-        return this.createEntry(link, key);
-      }.bind(this));
-    },
+  save: function () {
+    this.props.onSave(this.editor.getValue());
+  },
 
-    createEntry: function (link, key) {
-      return (
-        <li key={key}>
-          <a className={link.icon ? 'icon ' + link.icon : ''}
-            data-bypass={link.external ? 'true' : ''}
-            href={link.url}
-            onClick={link.onClick}
-            target={link.external ? '_blank' : ''}>
-            {link.title}
-          </a>
-        </li>
-      );
-    },
+  render: function () {
+    return (
+      <Modal dialogClassName="string-editor-modal" show={this.props.visible} onHide={this.closeModal}>
+        <Modal.Header closeButton={true}>
+          <Modal.Title>Edit text <span id="string-edit-header"></span></Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+          <div id="modal-error" className="hide alert alert-error"/>
+          <div id="string-editor-wrapper"><div ref="stringEditor" className="doc-code"></div></div>
+        </Modal.Body>
+        <Modal.Footer>
+          <button className="cancel-button btn" onClick={this.closeModal}><i className="icon fonticon-circle-x"></i> Cancel</button>
+          <button id="string-edit-save-btn" onClick={this.save} className="btn btn-success save">
+            <i className="fonticon-circle-check"></i> Save
+          </button>
+        </Modal.Footer>
+      </Modal>
+    );
+  }
+});
 
-    createSectionTitle: function (title) {
-      if (!title) {
-        return null;
-      }
 
-      return (
-        <li className="header-label">{title}</li>
-      );
-    },
+// Zen mode editing has very few options:
+// - It covers the full screen, hiding everything else
+// - Two themes: light & dark (choice stored in local storage)
+// - No save option, but has a 1-1 map with a <CodeEditor /> element which gets updated when the user leaves
+// - [Escape] closes the mode, as does clicking the shrink icon at the top right
+var ZenModeOverlay = React.createClass({
+  getDefaultProps: function () {
+    return {
+      mode: 'javascript',
+      defaultCode: '',
+      ignorableErrors: [],
+      onExit: null,
+      highlightActiveLine: false
+    };
+  },
+
+  themes: {
+    dark: 'idle_fingers',
+    light: 'dawn'
+  },
+
+  getInitialState: function () {
+    return this.getStoreState();
+  },
+
+  getStoreState: function () {
+    return {
+      theme: this.getZenTheme(),
+      code: this.props.defaultCode
+    };
+  },
+
+  getZenTheme: function () {
+    var selectedTheme = app.utils.localStorageGet('zenTheme');
+    return _.isUndefined(selectedTheme) ? 'dark' : selectedTheme;
+  },
+
+  onChange: function () {
+    this.setState(this.getStoreState());
+  },
+
+  componentDidMount: function () {
+    $(ReactDOM.findDOMNode(this.refs.exit)).tooltip({ placement: 'left' });
+    $(ReactDOM.findDOMNode(this.refs.theme)).tooltip({ placement: 'left' });
+  },
+
+  exitZenMode: function () {
+    this.props.onExit(this.getValue());
+  },
+
+  getValue: function () {
+    return this.refs.ace.getValue();
+  },
+
+  toggleTheme: function () {
+    var newTheme = (this.state.theme === 'dark') ? 'light' : 'dark';
+    this.setState({
+      theme: newTheme,
+      code: this.getValue()
+    });
+    app.utils.localStorageSet('zenTheme', newTheme);
+  },
 
-    createSection: function () {
-      return this.props.links.map(function (linkSection, key) {
-        if (linkSection.title && linkSection.links) {
-          return ([
-            this.createSectionTitle(linkSection.title),
-            this.createSectionLinks(linkSection.links)
-          ]);
-        }
+  setValue: function (code, lineNumber) {
+    lineNumber = lineNumber ? lineNumber : -1;
+    this.editor.setValue(code, lineNumber);
+  },
 
-        return this.createEntry(linkSection, 'el' + key);
+  render: function () {
+    var classes = 'full-page-editor-modal-wrapper zen-theme-' + this.state.theme;
 
-      }.bind(this));
-    },
+    var editorCommands = [{
+      name: 'close',
+      bindKey: { win: 'ESC', mac: 'ESC' },
+      exec: this.exitZenMode
+    }];
 
-    render: function () {
-      return (
-        <div className="dropdown">
-          <a className={"dropdown-toggle icon " + this.props.icon}
-          data-toggle="dropdown"
-          href="#"
-          data-bypass="true"></a>
-          <ul className="dropdown-menu arrow" role="menu" aria-labelledby="dLabel">
-            {this.createSection()}
+    return (
+      <div className={classes}>
+        <div className="zen-mode-controls">
+          <ul>
+            <li>
+              <span ref="exit"
+                className="fonticon fonticon-resize-small js-exit-zen-mode"
+                data-toggle="tooltip"
+                data-container=".zen-mode-controls .tooltips"
+                title="Exit zen mode (`esc`)"
+                onClick={this.exitZenMode}></span>
+            </li>
+            <li>
+              <span ref="theme"
+                className="fonticon fonticon-picture js-toggle-theme"
+                data-toggle="tooltip"
+                data-container=".zen-mode-controls .tooltips"
+                title="Switch zen theme"
+                onClick={this.toggleTheme}></span>
+            </li>
           </ul>
+          <div className="tooltips"></div>
         </div>
-      );
-    }
-  });
+        <CodeEditor
+          ref="ace"
+          autoFocus={true}
+          theme={this.themes[this.state.theme]}
+          defaultCode={this.props.defaultCode}
+          editorCommands={editorCommands}
+          ignorableErrors={this.props.ignorableErrors}
+          highlightActiveLine={this.props.highlightActiveLine}
+        />
+      </div>
+    );
+  }
+});
 
-  var TrayContents = React.createClass({
-    getChildren: function () {
-      var className = "tray show-tray " + this.props.className;
-      return (
-        <div key={1} id={this.props.id} className={className}>
-          {this.props.children}
-        </div>);
-    },
 
-    render: function () {
-      return (
-        <ReactCSSTransitionGroup transitionName="tray" transitionAppear={true} component="div" transitionAppearTimeout={500}
-          transitionEnterTimeout={500} transitionLeaveTimeout={300}>
-          {this.getChildren()}
-        </ReactCSSTransitionGroup>
-      );
-    }
-  });
+var Beautify = React.createClass({
+  noOfLines: function () {
+    return this.props.code.split(/\r\n|\r|\n/).length;
+  },
 
+  canBeautify: function () {
+    return this.noOfLines() === 1;
+  },
 
-  function connectToStores (Component, stores, getStateFromStores) {
+  addTooltip: function () {
+    if (this.canBeautify) {
+      $('.beautify-tooltip').tooltip({ placement: 'right' });
+    }
+  },
+
+  componentDidMount: function () {
+    this.addTooltip();
+  },
+
+  beautify: function (event) {
+    event.preventDefault();
+    var beautifiedCode = beautifyHelper(this.props.code);
+    this.props.beautifiedCode(beautifiedCode);
+    $('.beautify-tooltip').tooltip('hide');
+  },
+
+  render: function () {
+    if (!this.canBeautify()) {
+      return null;
+    }
 
-    var WrappingElement = React.createClass({
+    return (
+      <button
+        onClick={this.beautify}
+        className="beautify beautify_map btn btn-primary btn-small beautify-tooltip"
+        type="button"
+        data-toggle="tooltip"
+        title="Reformat your minified code to make edits to it."
+      >
+        beautify this code
+      </button>
+    );
+  }
+});
 
-      componentDidMount: function () {
-        stores.forEach(function (store) {
-          store.on('change', this.onChange, this);
-        }.bind(this));
-      },
+var PaddedBorderedBox = React.createClass({
+  render: function () {
+    return (
+      <div className="bordered-box">
+        <div className="padded-box">
+          {this.props.children}
+        </div>
+      </div>
+    );
+  }
+});
 
-      componentWillUnmount: function () {
-        stores.forEach(function (store) {
-          store.off('change', this.onChange);
-        }.bind(this));
-      },
+var Document = React.createClass({
+  propTypes: {
+    docIdentifier: React.PropTypes.string.isRequired,
+    docChecked: React.PropTypes.func.isRequired,
+    truncate: React.PropTypes.bool,
+    maxRows: React.PropTypes.number
+  },
+
+  getDefaultProps: function () {
+    return {
+      truncate: true,
+      maxRows: 500
+    };
+  },
+
+  onChange: function (e) {
+    e.preventDefault();
+    this.props.docChecked(this.props.doc.id, this.props.doc._rev);
+  },
+
+  getUrlFragment: function () {
+    if (!this.props.children) {
+      return '';
+    }
 
-      getInitialState: function () {
-        return getStateFromStores(this.props);
-      },
+    return (
+      <div className="doc-edit-symbol pull-right" title="Edit document">
+        {this.props.children}
+      </div>
+    );
+  },
+
+  getExtensionIcons: function () {
+    var extensions = FauxtonAPI.getExtensions('DocList:icons');
+    return _.map(extensions, function (Extension, i) {
+      return (<Extension doc={this.props.doc} key={i} />);
+    }, this);
+  },
+
+  getCheckbox: function () {
+    if (!this.props.isDeletable) {
+      return <div className="checkbox-dummy"></div>;
+    }
 
-      onChange: function () {
-        if (!this.isMounted()) {
-          return;
-        }
+    return (
+      <div className="checkbox inline">
+        <input
+          id={'checkbox-' + this.props.docIdentifier}
+          checked={this.props.checked}
+          data-checked={this.props.checked}
+          type="checkbox"
+          onChange={this.onChange}
+          className="js-row-select" />
+        <label onClick={this.onChange}
+          className="label-checkbox-doclist"
+          htmlFor={'checkbox-' + this.props.docIdentifier} />
+      </div>
+    );
+  },
 
-        this.setState(getStateFromStores(this.props));
-      },
+  onDoubleClick: function (e) {
+    this.props.onDoubleClick(this.props.docIdentifier, this.props.doc, e);
+  },
 
-      handleStoresChanged: function () {
-        if (this.isMounted()) {
-          this.setState(getStateFromStores(this.props));
-        }
-      },
+  getDocContent: function () {
+    if (_.isEmpty(this.props.docContent)) {
+      return null;
+    }
 
-      render: function () {
-        return <Component {...this.state} {...this.props} />;
-      }
+    // if need be, truncate the document
+    var content = this.props.docContent;
+    var isTruncated = false;
+    if (this.props.truncate) {
+      var result = Helpers.truncateDoc(this.props.docContent, this.props.maxRows);
+      isTruncated = result.isTruncated;
+      content = result.content;
+    }
 
-    });
+    return (
+      <div className="doc-data">
+        <pre className="prettyprint">{content}</pre>
+        {isTruncated ? <div className="doc-content-truncated">(truncated)</div> : null}
+      </div>
+    );
+  },
 
-    return WrappingElement;
+  render: function () {
+    return (
+      <div data-id={this.props.docIdentifier} onDoubleClick={this.onDoubleClick} className="doc-row">
+        <div className="custom-inputs">
+          {this.getCheckbox()}
+        </div>
+        <div className="doc-item">
+          <header>
+            <span className="header-keylabel">
+              {this.props.keylabel}
+            </span>
+            <span className="header-doc-id">
+              {this.props.header ? '"' + this.props.header + '"' : null}
+            </span>
+            {this.getUrlFragment()}
+            <div className="doc-item-extension-icons pull-right">{this.getExtensionIcons()}</div>
+          </header>
+          {this.getDocContent()}
+        </div>
+        <div className="clearfix"></div>
+      </div>
+    );
   }
+});
 
-  var TrayWrapper = React.createClass({
-    getDefaultProps: function () {
-      return {
-        className: ''
-      };
-    },
+var LoadLines = React.createClass({
+  render: function () {
+    return (
+      <div className="loading-lines">
+        <div id="line1"> </div>
+        <div id="line2"> </div>
+        <div id="line3"> </div>
+        <div id="line4"> </div>
+      </div>
+    );
+  }
+});
 
-    renderChildren: function () {
-      return React.Children.map(this.props.children, function (child, key) {
+var ConfirmButton = React.createClass({
+  propTypes: {
+    showIcon: React.PropTypes.bool,
+    id: React.PropTypes.string,
+    customIcon: React.PropTypes.string,
+    style: React.PropTypes.object,
+    buttonType: React.PropTypes.string,
+    'data-id': React.PropTypes.string,
+    onClick: React.PropTypes.func
+  },
+
+  getDefaultProps: function () {
+    return {
+      showIcon: true,
+      customIcon: 'fonticon-ok-circled',
+      buttonType: 'btn-success',
+      style: {},
+      'data-id': null,
+      onClick: function () { }
+    };
+  },
+
+  getIcon: function () {
+    if (!this.props.showIcon) {
+      return null;
+    }
+    return (
+      <i className={"icon " + this.props.customIcon} />
+    );
+  },
 
-        const props = {};
-        Object.keys(this.props).filter((k) => {
-          return this.props.hasOwnProperty(k);
-        }).map((k) => {
-          return props[k] = this.props[k];
-        });
+  render: function () {
+    const { onClick, buttonType, id, style, text } = this.props;
+    return (
+      <button
+        onClick={onClick}
+        type="submit"
+        data-id={this.props['data-id']}
+        className={'btn save ' + buttonType}
+        id={id}
+        style={style}
+      >
+        {this.getIcon()}
+        {text}
+      </button>
+    );
+  }
+});
 
-        return React.cloneElement(child, props);
-      }.bind(this));
-    },
+var MenuDropDown = React.createClass({
 
-    render: function () {
-      return (
-        <div>
-          {this.renderChildren()}
-        </div>
-      );
-    }
-  });
+  getDefaultProps: function () {
+    return {
+      icon: 'fonticon-plus-circled'
+    };
+  },
 
-  var APIBar = React.createClass({
-    propTypes: {
-      buttonVisible: React.PropTypes.bool.isRequired,
-      contentVisible: React.PropTypes.bool.isRequired,
-      docURL: React.PropTypes.string,
-      endpoint: React.PropTypes.string
-    },
+  createSectionLinks: function (links) {
+    if (!links) { return null; }
 
-    showCopiedMessage: function () {
-      FauxtonAPI.addNotification({
-        msg: 'The API URL has been copied to the clipboard.',
-        type: 'success',
-        clear: true
-      });
-    },
+    return links.map(function (link, key) {
+      return this.createEntry(link, key);
+    }.bind(this));
+  },
 
-    getDocIcon: function () {
-      if (!this.props.docURL) {
-        return null;
-      }
-      return (
-        <a
-          className="help-link"
-          data-bypass="true"
-          href={this.props.docURL}
-          target="_blank"
-        >
-          <i className="icon icon-question-sign"></i>
+  createEntry: function (link, key) {
+    return (
+      <li key={key}>
+        <a className={link.icon ? 'icon ' + link.icon : ''}
+          data-bypass={link.external ? 'true' : ''}
+          href={link.url}
+          onClick={link.onClick}
+          target={link.external ? '_blank' : ''}>
+          {link.title}
         </a>
-      );
-    },
+      </li>
+    );
+  },
+
+  createSectionTitle: function (title) {
+    if (!title) {
+      return null;
+    }
 
-    getTray: function () {
-      if (!this.props.contentVisible) {
-        return null;
+    return (
+      <li className="header-label">{title}</li>
+    );
+  },
+
+  createSection: function () {
+    return this.props.links.map(function (linkSection, key) {
+      if (linkSection.title && linkSection.links) {
+        return ([
+          this.createSectionTitle(linkSection.title),
+          this.createSectionLinks(linkSection.links)
+        ]);
       }
 
-      return (
-        <TrayContents className="tray show-tray api-bar-tray">
-          <div className="input-prepend input-append">
-            <span className="add-on">
-              API URL
-              {this.getDocIcon()}
-            </span>
+      return this.createEntry(linkSection, 'el' + key);
 
-            <FauxtonComponents.ClipboardWithTextField
-              onClipBoardClick={this.showCopiedMessage}
-              text="Copy URL"
-              textToCopy={this.props.endpoint}
-              showCopyIcon={false}
-              uniqueKey="clipboard-apiurl" />
-
-            <div className="add-on">
-              <a
-                data-bypass="true"
-                href={this.props.endpoint}
-                target="_blank"
-                className="btn"
-              >
-                View JSON
-              </a>
-            </div>
-          </div>
-        </TrayContents>
-      );
-    },
+    }.bind(this));
+  },
 
-    toggleTrayVisibility: function () {
-      Actions.toggleApiBarVisibility(!this.props.contentVisible);
-    },
+  render: function () {
+    return (
+      <div className="dropdown">
+        <a className={"dropdown-toggle icon " + this.props.icon}
+        data-toggle="dropdown"
+        href="#"
+        data-bypass="true"></a>
+        <ul className="dropdown-menu arrow" role="menu" aria-labelledby="dLabel">
+          {this.createSection()}
+        </ul>
+      </div>
+    );
+  }
+});
+
+var TrayContents = React.createClass({
+  getChildren: function () {
+    var className = "tray show-tray " + this.props.className;
+    return (
+      <div key={1} id={this.props.id} className={className}>
+        {this.props.children}
+      </div>);
+  },
+
+  render: function () {
+    return (
+      <ReactCSSTransitionGroup transitionName="tray" transitionAppear={true} component="div" transitionAppearTimeout={500}
+        transitionEnterTimeout={500} transitionLeaveTimeout={300}>
+        {this.getChildren()}
+      </ReactCSSTransitionGroup>
+    );
+  }
+});
+
+
+function connectToStores (Component, stores, getStateFromStores) {
+
+  var WrappingElement = React.createClass({
 
     componentDidMount: function () {
-      $('body').on('click.APIBar', function (e) {
-        if ($(e.target).closest('.api-bar-tray,.control-toggle-api-url').length === 0) {
-          Actions.toggleApiBarVisibility(false);
-        }
+      stores.forEach(function (store) {
+        store.on('change', this.onChange, this);
       }.bind(this));
     },
 
     componentWillUnmount: function () {
-      $('body').off('click.APIBar');
+      stores.forEach(function (store) {
+        store.off('change', this.onChange);
+      }.bind(this));
     },
 
-    render: function () {
-      if (!this.props.buttonVisible || !this.props.endpoint) {
-        return null;
+    getInitialState: function () {
+      return getStateFromStores(this.props);
+    },
+
+    onChange: function () {
+      if (!this.isMounted()) {
+        return;
       }
 
-      return (
-        <div>
-          <ToggleHeaderButton
-            containerClasses="header-control-box control-toggle-api-url"
-            title="API URL"
-            fonticon="fonticon-link"
-            text="API"
-            toggleCallback={this.toggleTrayVisibility} />
-
-          {this.getTray()}
-        </div>
-      );
+      this.setState(getStateFromStores(this.props));
+    },
+
+    handleStoresChanged: function () {
+      if (this.isMounted()) {
+        this.setState(getStateFromStores(this.props));
+      }
+    },
+
+    render: function () {
+      return <Component {...this.state} {...this.props} />;
     }
+
   });
 
-  var ApiBarController = React.createClass({
+  return WrappingElement;
+}
+
+var TrayWrapper = React.createClass({
+  getDefaultProps: function () {
+    return {
+      className: ''
+    };
+  },
+
+  renderChildren: function () {
+    return React.Children.map(this.props.children, function (child, key) {
 
-    getWrap: function () {
-      return connectToStores(TrayWrapper, [componentStore], function () {
-        return {
-          buttonVisible: componentStore.getIsAPIBarButtonVisible(),
-          contentVisible: componentStore.getIsAPIBarVisible(),
-          endpoint: componentStore.getEndpoint(),
-          docURL: componentStore.getDocURL()
-        };
+      const props = {};
+      Object.keys(this.props).filter((k) => {
+        return this.props.hasOwnProperty(k);
+      }).map((k) => {
+        return props[k] = this.props[k];
       });
-    },
 
-    render: function () {
-      var TrayWrapper = this.getWrap();
-      return (
-        <TrayWrapper>
-          <APIBar buttonVisible={true} contentVisible={false} />
-        </TrayWrapper>
-      );
+      return React.cloneElement(child, props);
+    }.bind(this));
+  },
+
+  render: function () {
+    return (
+      <div>
+        {this.renderChildren()}
+      </div>
+    );
+  }
+});
+
+var APIBar = React.createClass({
+  propTypes: {
+    buttonVisible: React.PropTypes.bool.isRequired,
+    contentVisible: React.PropTypes.bool.isRequired,
+    docURL: React.PropTypes.string,
+    endpoint: React.PropTypes.string
+  },
+
+  showCopiedMessage: function () {
+    FauxtonAPI.addNotification({
+      msg: 'The API URL has been copied to the clipboard.',
+      type: 'success',
+      clear: true
+    });
+  },
+
+  getDocIcon: function () {
+    if (!this.props.docURL) {
+      return null;
     }
-  });
+    return (
+      <a
+        className="help-link"
+        data-bypass="true"
+        href={this.props.docURL}
+        target="_blank"
+      >
+        <i className="icon icon-question-sign"></i>
+      </a>
+    );
+  },
 
-  var DeleteDatabaseModal = React.createClass({
+  getTray: function () {
+    if (!this.props.contentVisible) {
+      return null;
+    }
 
-    getInitialState: function () {
-      return {
-        inputValue: '',
-        disableSubmit: true
-      };
-    },
+    return (
+      <TrayContents className="tray show-tray api-bar-tray">
+        <div className="input-prepend input-append">
+          <span className="add-on">
+            API URL
+            {this.getDocIcon()}
+          </span>
+
+          <FauxtonComponents.ClipboardWithTextField
+            onClipBoardClick={this.showCopiedMessage}
+            text="Copy URL"
+            textToCopy={this.props.endpoint}
+            showCopyIcon={false}
+            uniqueKey="clipboard-apiurl" />
+
+          <div className="add-on">
+            <a
+              data-bypass="true"
+              href={this.props.endpoint}
+              target="_blank"
+              className="btn"
+            >
+              View JSON
+            </a>
+          </div>
+        </div>
+      </TrayContents>
+    );
+  },
 
-    propTypes: {
-      showHide: React.PropTypes.func.isRequired,
-      modalProps: React.PropTypes.object
-    },
+  toggleTrayVisibility: function () {
+    Actions.toggleApiBarVisibility(!this.props.contentVisible);
+  },
 
-    close: function (e) {
-      if (e) {
-        e.preventDefault();
+  componentDidMount: function () {
+    $('body').on('click.APIBar', function (e) {
+      if ($(e.target).closest('.api-bar-tray,.control-toggle-api-url').length === 0) {
+        Actions.toggleApiBarVisibility(false);
       }
+    }.bind(this));
+  },
 
-      this.setState({
-        inputValue: '',
-        disableSubmit: true
-      });
+  componentWillUnmount: function () {
+    $('body').off('click.APIBar');
+  },
 
-      this.props.showHide({showModal: false});
-    },
+  render: function () {
+    if (!this.props.buttonVisible || !this.props.endpoint) {
+      return null;
+    }
 
-    open: function () {
-      this.props.showHide({showModal: true});
-    },
+    return (
+      <div>
+        <ToggleHeaderButton
+          containerClasses="header-control-box control-toggle-api-url"
+          title="API URL"
+          fonticon="fonticon-link"
+          text="API"
+          toggleCallback={this.toggleTrayVisibility} />
+
+        {this.getTray()}
+      </div>
+    );
+  }
+});
 
-    getDatabaseName: function () {
-      return this.props.modalProps.dbId.trim();
-    },
+var ApiBarController = React.createClass({
 
-    onInputChange: function (e) {
-      var val = encodeURIComponent(e.target.value.trim());
+  getWrap: function () {
+    return connectToStores(TrayWrapper, [componentStore], function () {
+      return {
+        buttonVisible: componentStore.getIsAPIBarButtonVisible(),
+        contentVisible: componentStore.getIsAPIBarVisible(),
+        endpoint: componentStore.getEndpoint(),
+        docURL: componentStore.getDocURL()
+      };
+    });
+  },
 
-      this.setState({
-        inputValue: val
-      });
+  render: function () {
+    var TrayWrapper = this.getWrap();
+    return (
+      <TrayWrapper>
+        <APIBar buttonVisible={true} contentVisible={false} />
+      </TrayWrapper>
+    );
+  }
+});
 
-      this.setState({
-        disableSubmit: val !== this.getDatabaseName()
-      });
-    },
+var DeleteDatabaseModal = React.createClass({
+
+  getInitialState: function () {
+    return {
+      inputValue: '',
+      disableSubmit: true
+    };
+  },
+
+  propTypes: {
+    showHide: React.PropTypes.func.isRequired,
+    modalProps: React.PropTypes.object
+  },
 
-    onDeleteClick: function (e) {
+  close: function (e) {
+    if (e) {
       e.preventDefault();
+    }
 
-      Actions.deleteDatabase(this.getDatabaseName());
-    },
+    this.setState({
+      inputValue: '',
+      disableSubmit: true
+    });
 
-    onInputKeypress: function (e) {
-      if (e.keyCode === 13 && this.state.disableSubmit !== true) {
-        Actions.deleteDatabase(this.getDatabaseName());
-      }
-    },
+    this.props.showHide({showModal: false});
+  },
 
-    render: function () {
-      var isSystemDatabase = this.props.modalProps.isSystemDatabase;
-      var showDeleteModal = this.props.modalProps.showDeleteModal;
-      var dbId = this.props.modalProps.dbId;
+  open: function () {
+    this.props.showHide({showModal: true});
+  },
 
-      var warning = isSystemDatabase ? (
-        <p style={{color: '#d14'}} className="warning">
-          <b>You are about to delete a system database, be careful!</b>
-        </p>
-      ) : null;
+  getDatabaseName: function () {
+    return this.props.modalProps.dbId.trim();
+  },
 
-      return (
-        <Modal dialogClassName="delete-db-modal" show={showDeleteModal} onHide={this.close}>
-          <Modal.Header closeButton={true}>
-            <Modal.Title>Delete Database</Modal.Title>
-          </Modal.Header>
-          <Modal.Body>
-            {warning}
-            <p>
-              Warning: This action will permanently delete <code>{dbId}</code>.
-              To confirm the deletion of the database and all of the
-              database's documents, you must enter the database's name.
-            </p>
-            <input
-              type="text"
-              className="input-block-level"
-              onKeyUp={this.onInputKeypress}
-              onChange={this.onInputChange}
-              autoFocus={true} />
-          </Modal.Body>
-          <Modal.Footer>
-            <a href="#" onClick={this.close} data-bypass="true" className="cancel-link">Cancel</a>
-            <button
-              disabled={this.state.disableSubmit}
-              onClick={this.onDeleteClick}
-              className="btn btn-danger delete">
-              <i className="icon fonticon-cancel-circled" /> Delete
-            </button>
-          </Modal.Footer>
-        </Modal>
-      );
-    }
-  });
+  onInputChange: function (e) {
+    var val = encodeURIComponent(e.target.value.trim());
 
-  const TabElement = ({selected, text, onChange, iconClass}) => {
+    this.setState({
+      inputValue: val
+    });
 
-    const additionalClass = selected ? 'tab-element-checked' : '';
+    this.setState({
+      disableSubmit: val !== this.getDatabaseName()
+    });
+  },
 
-    return (
-      <li className={`component-tab-element ${additionalClass}`}>
+  onDeleteClick: function (e) {
+    e.preventDefault();
 
-        <label>
-          <div className="tab-element-indicator-wrapper">
-            <div className="tab-element-indicator"></div>
-          </div>
-          <div className="tab-element-content">
-            <i className={iconClass}></i>
-            <input
-              type="radio"
-              value={text}
-              checked={selected}
-              onChange={onChange} />
-
-              {text}
-          </div>
-        </label>
-      </li>
+    Actions.deleteDatabase(this.getDatabaseName());
+  },
+
+  onInputKeypress: function (e) {
+    if (e.keyCode === 13 && this.state.disableSubmit !== true) {
+      Actions.deleteDatabase(this.getDatabaseName());
+    }
+  },
+
+  render: function () {
+    var isSystemDatabase = this.props.modalProps.isSystemDatabase;
+    var showDeleteModal = this.props.modalProps.showDeleteModal;
+    var dbId = this.props.modalProps.dbId;
+
+    var warning = isSystemDatabase ? (
+      <p style={{color: '#d14'}} className="warning">
+        <b>You are about to delete a system database, be careful!</b>
+      </p>
+    ) : null;
 
-    );
-  };
-  TabElement.propTypes = {
-    selected: React.PropTypes.bool.isRequired,
-    text: React.PropTypes.string.isRequired,
-    onChange: React.PropTypes.func.isRequired,
-    iconClass: React.PropTypes.string,
-  };
-
-  const TabElementWrapper = ({children}) => {
     return (
-      <ul className="nav nav-tabs component-tab-element-wrapper">
-        {children}
-      </ul>
+      <Modal dialogClassName="delete-db-modal" show={showDeleteModal} onHide={this.close}>
+        <Modal.Header closeButton={true}>
+          <Modal.Title>Delete Database</Modal.Title>
+        </Modal.Header>
+        <Modal.Body>
+          {warning}
+          <p>
+            Warning: This action will permanently delete <code>{dbId}</code>.
+            To confirm the deletion of the database and all of the
+            database's documents, you must enter the database's name.
+          </p>
+          <input
+            type="text"
+            className="input-block-level"
+            onKeyUp={this.onInputKeypress}
+            onChange={this.onInputChange}
+            autoFocus={true} />
+        </Modal.Body>
+        <Modal.Footer>
+          <a href="#" onClick={this.close} data-bypass="true" className="cancel-link">Cancel</a>
+          <button
+            disabled={this.state.disableSubmit}
+            onClick={this.onDeleteClick}
+            className="btn btn-danger delete">
+            <i className="icon fonticon-cancel-circled" /> Delete
+          </button>
+        </Modal.Footer>
+      </Modal>
     );
-  };
-
-
-  return {
-    BadgeList: BadgeList,
-    Badge: Badge,
-    BulkActionComponent: BulkActionComponent,
-    ConfirmButton: ConfirmButton,
-    ToggleHeaderButton: ToggleHeaderButton,
-    StyledSelect: StyledSelect,
-    CodeEditorPanel: CodeEditorPanel,
-    CodeEditor: CodeEditor,
-    StringEditModal: StringEditModal,
-    ZenModeOverlay: ZenModeOverlay,
-    Beautify: Beautify,
-    PaddedBorderedBox: PaddedBorderedBox,
-    Document: Document,
-    LoadLines: LoadLines,
-    MenuDropDown: MenuDropDown,
-    TrayContents: TrayContents,
-    TrayWrapper: TrayWrapper,
-    connectToStores: connectToStores,
-    ApiBarController: ApiBarController,
-    renderMenuDropDown: function (el, opts) {
-      ReactDOM.render(<MenuDropDown icon="fonticon-vertical-ellipsis" links={opts.links} />, el);
-    },
-    removeMenuDropDown: function (el) {
-      ReactDOM.unmountComponentAtNode(el);
-    },
-    DeleteDatabaseModal: DeleteDatabaseModal,
-    TabElement: TabElement,
-    TabElementWrapper: TabElementWrapper
-  };
-
+  }
 });
+
+const TabElement = ({selected, text, onChange, iconClass}) => {
+
+  const additionalClass = selected ? 'tab-element-checked' : '';
+
+  return (
+    <li className={`component-tab-element ${additionalClass}`}>
+
+      <label>
+        <div className="tab-element-indicator-wrapper">
+          <div className="tab-element-indicator"></div>
+        </div>
+        <div className="tab-element-content">
+          <i className={iconClass}></i>
+          <input
+            type="radio"
+            value={text}
+            checked={selected}
+            onChange={onChange} />
+
+            {text}
+        </div>
+      </label>
+    </li>
+
+  );
+};
+TabElement.propTypes = {
+  selected: React.PropTypes.bool.isRequired,
+  text: React.PropTypes.string.isRequired,
+  onChange: React.PropTypes.func.isRequired,
+  iconClass: React.PropTypes.string,
+};
+
+const TabElementWrapper = ({children}) => {
+  return (
+    <ul className="nav nav-tabs component-tab-element-wrapper">
+      {children}
+    </ul>
+  );
+};
+
+
+export default {
+  BadgeList: BadgeList,
+  Badge: Badge,
+  BulkActionComponent: BulkActionComponent,
+  ConfirmButton: ConfirmButton,
+  ToggleHeaderButton: ToggleHeaderButton,
+  StyledSelect: StyledSelect,
+  CodeEditorPanel: CodeEditorPanel,
+  CodeEditor: CodeEditor,
+  StringEditModal: StringEditModal,
+  ZenModeOverlay: ZenModeOverlay,
+  Beautify: Beautify,
+  PaddedBorderedBox: PaddedBorderedBox,
+  Document: Document,
+  LoadLines: LoadLines,
+  MenuDropDown: MenuDropDown,
+  TrayContents: TrayContents,
+  TrayWrapper: TrayWrapper,
+  connectToStores: connectToStores,
+  ApiBarController: ApiBarController,
+  renderMenuDropDown: function (el, opts) {
+    ReactDOM.render(<MenuDropDown icon="fonticon-vertical-ellipsis" links={opts.links} />, el);
+  },
+  removeMenuDropDown: function (el) {
+    ReactDOM.unmountComponentAtNode(el);
+  },
+  DeleteData

<TRUNCATED>

Mime
View raw message