You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2016/11/07 08:33:46 UTC
[3/3] fauxton commit: updated refs/heads/master to 369c326
break react components into individual files
Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/369c3265
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/369c3265
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/369c3265
Branch: refs/heads/master
Commit: 369c3265498f32a1f8fa793f3d5fd0bca456b0df
Parents: 0290732
Author: Garren Smith <ga...@gmail.com>
Authored: Wed Nov 2 15:04:42 2016 +0200
Committer: Garren Smith <ga...@gmail.com>
Committed: Mon Nov 7 10:33:01 2016 +0200
----------------------------------------------------------------------
app/addons/components/base.js | 7 +-
app/addons/components/components/apibar.js | 152 ++
app/addons/components/components/badge.js | 83 +
app/addons/components/components/beautify.js | 59 +
app/addons/components/components/bulkaction.js | 109 ++
app/addons/components/components/codeeditor.js | 370 ++++
.../components/components/codeeditorpanel.js | 147 ++
.../components/components/confirmbutton.js | 66 +
.../components/deletedatabasemodal.js | 119 ++
app/addons/components/components/document.js | 126 ++
app/addons/components/components/loadlines.js | 21 +
.../components/components/menudropdown.js | 82 +
.../components/components/paddedborderbox.js | 25 +
.../components/components/stringeditmodal.js | 95 +
.../components/components/styledselect.js | 39 +
app/addons/components/components/tabelement.js | 55 +
.../components/components/toggleheaderbutton.js | 46 +
app/addons/components/components/tray.js | 108 ++
.../components/components/zenmodeoverlay.js | 131 ++
.../components/react-components.react.jsx | 1657 +-----------------
.../tests/paddedBorderedBoxSpec.react.jsx | 1 +
.../fauxton/tests/componentsSpec.react.jsx | 22 +-
22 files changed, 1884 insertions(+), 1636 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/base.js
----------------------------------------------------------------------
diff --git a/app/addons/components/base.js b/app/addons/components/base.js
index 1f6481d..47789ef 100644
--- a/app/addons/components/base.js
+++ b/app/addons/components/base.js
@@ -12,7 +12,6 @@
import "./assets/less/components.less";
-const Components = {};
-Components.initialize = function () {};
-
-export default Components;
+export default {
+ initialize () {}
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/apibar.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/apibar.js b/app/addons/components/components/apibar.js
new file mode 100644
index 0000000..c853cc8
--- /dev/null
+++ b/app/addons/components/components/apibar.js
@@ -0,0 +1,152 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+import FauxtonAPI from "../../../core/api";
+import {TrayContents, TrayWrapper, connectToStores} from './tray';
+import FauxtonComponents from "../../fauxton/components.react";
+import Actions from "../actions";
+import Stores from "../stores";
+import {ToggleHeaderButton} from './toggleheaderbutton';
+const { componentStore } = Stores;
+
+export const APIBar = React.createClass({
+ propTypes: {
+ buttonVisible: React.PropTypes.bool.isRequired,
+ contentVisible: React.PropTypes.bool.isRequired,
+ docURL: React.PropTypes.string,
+ endpoint: React.PropTypes.string
+ },
+
+ showCopiedMessage () {
+ FauxtonAPI.addNotification({
+ msg: 'The API URL has been copied to the clipboard.',
+ type: 'success',
+ clear: true
+ });
+ },
+
+ getDocIcon () {
+ 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>
+ );
+ },
+
+ getTray () {
+ if (!this.props.contentVisible) {
+ return null;
+ }
+
+ 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>
+ );
+ },
+
+ toggleTrayVisibility () {
+ Actions.toggleApiBarVisibility(!this.props.contentVisible);
+ },
+
+ componentDidMount () {
+ $('body').on('click.APIBar', function (e) {
+
+ if (!$('.show-tray.api-bar-tray').length) {
+ return;
+ }
+
+ if ($(e.target).closest('.api-bar-tray,.control-toggle-api-url').length === 0) {
+ Actions.toggleApiBarVisibility(false);
+ }
+ }.bind(this));
+ },
+
+ componentWillUnmount () {
+ $('body').off('click.APIBar');
+ },
+
+ render () {
+ if (!this.props.buttonVisible || !this.props.endpoint) {
+ return null;
+ }
+
+ 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>
+ );
+ }
+});
+
+export const ApiBarController = React.createClass({
+
+ getWrap () {
+ return connectToStores(TrayWrapper, [componentStore], function () {
+ return {
+ buttonVisible: componentStore.getIsAPIBarButtonVisible(),
+ contentVisible: componentStore.getIsAPIBarVisible(),
+ endpoint: componentStore.getEndpoint(),
+ docURL: componentStore.getDocURL()
+ };
+ });
+ },
+
+ render () {
+ var TrayWrapper = this.getWrap();
+ return (
+ <TrayWrapper>
+ <APIBar buttonVisible={true} contentVisible={false} />
+ </TrayWrapper>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/badge.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/badge.js b/app/addons/components/components/badge.js
new file mode 100644
index 0000000..29b2599
--- /dev/null
+++ b/app/addons/components/components/badge.js
@@ -0,0 +1,83 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+
+export const BadgeList = React.createClass({
+
+ propTypes: {
+ elements: React.PropTypes.array.isRequired,
+ removeBadge: React.PropTypes.func.isRequired
+ },
+
+ getDefaultProps () {
+ return {
+ getLabel (el) {
+ return el;
+ },
+
+ getId (el) {
+ return el;
+ }
+ };
+ },
+
+ getBadges () {
+ 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 (label, el) {
+ this.props.removeBadge(label, el);
+ },
+
+ render () {
+ return (
+ <ul className="component-badgelist">
+ {this.getBadges()}
+ </ul>
+ );
+ }
+});
+
+export const Badge = React.createClass({
+ propTypes: {
+ label: React.PropTypes.string.isRequired,
+ remove: React.PropTypes.func.isRequired
+ },
+
+ remove (e) {
+ e.preventDefault();
+ this.props.remove(this.props.label, this.props.id);
+ },
+
+ render () {
+ return (
+ <li className="component-badge">
+ <span className="label label-info">{this.props.label}</span>
+ <a
+ href="#"
+ className="label label-info remove-filter"
+ onClick={this.remove} data-bypass="true"
+ >
+ ×
+ </a>
+ </li>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/beautify.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/beautify.js b/app/addons/components/components/beautify.js
new file mode 100644
index 0000000..3ff3519
--- /dev/null
+++ b/app/addons/components/components/beautify.js
@@ -0,0 +1,59 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+import beautifyHelper from "../../../../assets/js/plugins/beautify";
+
+export const Beautify = React.createClass({
+ noOfLines () {
+ return this.props.code.split(/\r\n|\r|\n/).length;
+ },
+
+ canBeautify () {
+ return this.noOfLines() === 1;
+ },
+
+ addTooltip () {
+ if (this.canBeautify) {
+ $('.beautify-tooltip').tooltip({ placement: 'right' });
+ }
+ },
+
+ componentDidMount () {
+ this.addTooltip();
+ },
+
+ beautify (event) {
+ event.preventDefault();
+ var beautifiedCode = beautifyHelper(this.props.code);
+ this.props.beautifiedCode(beautifiedCode);
+ $('.beautify-tooltip').tooltip('hide');
+ },
+
+ render () {
+ if (!this.canBeautify()) {
+ return null;
+ }
+
+ 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>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/bulkaction.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/bulkaction.js b/app/addons/components/components/bulkaction.js
new file mode 100644
index 0000000..d16e072
--- /dev/null
+++ b/app/addons/components/components/bulkaction.js
@@ -0,0 +1,109 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+import {OverlayTrigger, Popover} from "react-bootstrap";
+
+export const 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 () {
+ return {
+ disabled: false,
+ title: 'Select rows that can be...',
+ bulkIcon: 'fonticon-trash',
+ buttonTitle: 'Delete all selected',
+ dropdownContentText: 'Deleted',
+ enableOverlay: false
+ };
+ },
+
+ render () {
+ return (
+ <div className="bulk-action-component">
+ <div className="bulk-action-component-selector-group">
+ {this.getMasterSelector()}
+ {this.getMultiSelectOptions()}
+ </div>
+ </div>
+ );
+ },
+
+ getMultiSelectOptions () {
+ if (!this.props.hasSelectedItem) {
+ return null;
+ }
+
+ return (
+ <button
+ onClick={this.props.removeItem}
+ className={'fonticon ' + this.props.bulkIcon}
+ title={this.props.buttonTitle} />
+ );
+ },
+
+ getPopupContent () {
+ return (
+ <ul className="bulk-action-component-popover-actions">
+ <li onClick={this.selectAll} >
+ <i className="icon fonticon-cancel"></i> {this.props.dropdownContentText}
+ </li>
+ </ul>
+ );
+ },
+
+ selectAll () {
+ this.refs.bulkActionPopover.hide();
+ this.props.selectAll();
+ },
+
+ getOverlay () {
+ 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>
+ );
+ },
+
+ getMasterSelector () {
+ 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>
+ );
+ },
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/codeeditor.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/codeeditor.js b/app/addons/components/components/codeeditor.js
new file mode 100644
index 0000000..f8201dc
--- /dev/null
+++ b/app/addons/components/components/codeeditor.js
@@ -0,0 +1,370 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+import FauxtonAPI from "../../../core/api";
+import ace from "brace";
+import {StringEditModal} from './stringeditmodal';
+
+require('brace/mode/javascript');
+require('brace/mode/json');
+require('brace/theme/idle_fingers');
+
+export const CodeEditor = React.createClass({
+ getDefaultProps () {
+ 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 () {},
+ blur () {}
+ };
+ },
+
+ getInitialState () {
+ return {
+ originalCode: this.props.defaultCode,
+
+ // these are all related to the (optional) string edit modal
+ stringEditModalVisible: false,
+ stringEditIconVisible: false,
+ stringEditIconStyle: {},
+ stringEditModalValue: ''
+ };
+ },
+
+ hasChanged () {
+ return !_.isEqual(this.state.originalCode, this.getValue());
+ },
+
+ clearChanges () {
+ this.setState({
+ originalCode: this.getValue()
+ });
+ },
+
+ setupAce (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);
+
+ if (this.props.autoFocus) {
+ this.editor.focus();
+ }
+ },
+
+ addCommands () {
+ _.each(this.props.editorCommands, function (command) {
+ this.editor.commands.addCommand(command);
+ }, this);
+ },
+
+ setupEvents () {
+ 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.notifyUnsavedChanges) {
+ $(window).on('beforeunload.editor_' + this.props.id, _.bind(this.quitWarningMsg));
+ FauxtonAPI.beforeUnload('editor_' + this.props.id, _.bind(this.quitWarningMsg, this));
+ }
+ },
+
+ onBlur () {
+ this.props.blur(this.getValue());
+ },
+
+ onContentChange () {
+ if (this.props.setHeightToLineCount) {
+ this.setHeightToLineCount();
+ }
+ this.props.change(this.getValue());
+ },
+
+ quitWarningMsg () {
+ if (this.hasChanged()) {
+ return 'Your changes have not been saved. Click Cancel to return to the document, or OK to proceed.';
+ }
+ },
+
+ removeEvents () {
+ if (this.props.notifyUnsavedChanges) {
+ $(window).off('beforeunload.editor_' + this.props.id);
+ FauxtonAPI.removeBeforeUnload('editor_' + this.props.id);
+ }
+ },
+
+ setHeightToLineCount () {
+ var numLines = this.editor.getSession().getDocument().getLength();
+ var maxLines = (numLines > this.props.maxLines) ? this.props.maxLines : numLines;
+ this.editor.setOptions({
+ maxLines: maxLines
+ });
+ },
+
+ componentDidMount () {
+ this.setupAce(this.props, true);
+ this.setupEvents();
+
+ if (this.props.autoFocus) {
+ this.editor.focus();
+ }
+ },
+
+ componentWillUnmount () {
+ this.removeEvents();
+ this.editor.destroy();
+ },
+
+ componentWillReceiveProps (nextProps) {
+ this.setupAce(nextProps, false);
+ },
+
+ getAnnotations () {
+ return this.editor.getSession().getAnnotations();
+ },
+
+ isIgnorableError (msg) {
+ return _.contains(this.props.ignorableErrors, msg);
+ },
+
+ removeIgnorableAnnotations () {
+ 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));
+ },
+
+ showHideEditStringGutterIcon (e) {
+ if (this.hasErrors() || !this.parseLineForStringMatch()) {
+ this.setState({ stringEditIconVisible: false });
+ return false;
+ }
+
+ this.setState({
+ stringEditIconVisible: true,
+ stringEditIconStyle: {
+ top: this.getGutterIconPosition()
+ }
+ });
+
+ return true;
+ },
+
+ updateEditStringGutterIconPosition () {
+ if (!this.state.stringEditIconVisible) {
+ return;
+ }
+ this.setState({
+ stringEditIconStyle: {
+ top: this.getGutterIconPosition()
+ }
+ });
+ },
+
+ getGutterIconPosition () {
+ var rowHeight = this.getRowHeight();
+ var scrollTop = this.editor.session.getScrollTop();
+ var positionFromTop = (rowHeight * this.documentToScreenRow(this.getSelectionStart().row)) - scrollTop;
+ return positionFromTop + 'px';
+ },
+
+ parseLineForStringMatch () {
+ 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 false;
+ },
+
+ openStringEditModal () {
+ 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 (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 () {
+ this.setState({
+ stringEditModalVisible: false
+ });
+ },
+
+ hasErrors () {
+ return !_.every(this.getAnnotations(), function (error) {
+ return this.isIgnorableError(error.raw);
+ }, this);
+ },
+
+ setReadOnly (readonly) {
+ this.editor.setReadOnly(readonly);
+ },
+
+ setValue (code, lineNumber) {
+ lineNumber = lineNumber ? lineNumber : -1;
+ this.editor.setValue(code, lineNumber);
+ },
+
+ getValue () {
+ return this.editor.getValue();
+ },
+
+ getEditor () {
+ return this;
+ },
+
+ getLine (lineNum) {
+ return this.editor.session.getLine(lineNum);
+ },
+
+ getSelectionStart () {
+ return this.editor.getSelectionRange().start;
+ },
+
+ getSelectionEnd () {
+ return this.editor.getSelectionRange().end;
+ },
+
+ getRowHeight () {
+ return this.editor.renderer.layerConfig.lineHeight;
+ },
+
+ isRowExpanded (row) {
+ return !this.editor.getSession().isRowFolded(row);
+ },
+
+ documentToScreenRow (row) {
+ return this.editor.getSession().documentToScreenRow(row, 0);
+ },
+
+ replaceCurrentLine (replacement) {
+ this.editor.getSelection().selectLine();
+ this.editor.insert(replacement);
+ this.editor.getSelection().moveCursorUp();
+ },
+
+ render () {
+ 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>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/codeeditorpanel.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/codeeditorpanel.js b/app/addons/components/components/codeeditorpanel.js
new file mode 100644
index 0000000..92e3693
--- /dev/null
+++ b/app/addons/components/components/codeeditorpanel.js
@@ -0,0 +1,147 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+import {CodeEditor} from './codeeditor';
+import {StringEditModal} from './stringeditmodal';
+import {Beautify} from './beautify';
+import {ZenModeOverlay} from './zenmodeoverlay';
+
+/**
+ * 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.
+ */
+export const CodeEditorPanel = React.createClass({
+ getDefaultProps () {
+ return {
+ id: 'code-editor',
+ className: '',
+ defaultCode: '',
+ title: '',
+ docLink: '',
+ allowZenMode: true,
+ blur () {}
+ };
+ },
+
+ getInitialState () {
+ return this.getStoreState();
+ },
+
+ getStoreState () {
+ return {
+ zenModeEnabled: false,
+ code: this.props.defaultCode
+ };
+ },
+
+ componentWillReceiveProps (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 () {
+ if (this.props.allowZenMode) {
+ return <span className="fonticon fonticon-resize-full zen-editor-icon" title="Enter Zen mode" onClick={this.enterZenMode}></span>;
+ }
+ },
+
+ getDocIcon () {
+ 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 () {
+ if (this.state.zenModeEnabled) {
+ return (
+ <ZenModeOverlay
+ defaultCode={this.state.code}
+ mode={this.props.mode}
+ ignorableErrors={this.ignorableErrors}
+ onExit={this.exitZenMode} />
+ );
+ }
+ },
+
+ enterZenMode () {
+ this.setState({
+ zenModeEnabled: true,
+ code: this.refs.codeEditor.getValue()
+ });
+ },
+
+ exitZenMode (content) {
+ this.setState({ zenModeEnabled: false });
+ this.getEditor().setValue(content);
+ },
+
+ getEditor () {
+ return this.refs.codeEditor;
+ },
+
+ getValue () {
+ return this.getEditor().getValue();
+ },
+
+ beautify (code) {
+ this.setState({ code: code });
+ this.getEditor().setValue(code);
+ },
+
+ update () {
+ this.getEditor().setValue(this.state.code);
+ },
+
+ render () {
+ 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>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/confirmbutton.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/confirmbutton.js b/app/addons/components/components/confirmbutton.js
new file mode 100644
index 0000000..72e157e
--- /dev/null
+++ b/app/addons/components/components/confirmbutton.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.
+
+import React from "react";
+import ReactDOM from "react-dom";
+
+export const 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,
+ disabled: React.PropTypes.bool,
+ },
+
+ getDefaultProps () {
+ return {
+ disabled: false,
+ showIcon: true,
+ customIcon: 'fonticon-ok-circled',
+ buttonType: 'btn-success',
+ style: {},
+ 'data-id': null,
+ onClick () { }
+ };
+ },
+
+ getIcon () {
+ if (!this.props.showIcon) {
+ return null;
+ }
+ return (
+ <i className={"icon " + this.props.customIcon} />
+ );
+ },
+
+ render () {
+ const { onClick, buttonType, id, style, text, disabled } = this.props;
+ return (
+ <button
+ onClick={onClick}
+ type="submit"
+ disabled={disabled}
+ data-id={this.props['data-id']}
+ className={'btn save ' + buttonType}
+ id={id}
+ style={style}
+ >
+ {this.getIcon()}
+ {text}
+ </button>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/deletedatabasemodal.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/deletedatabasemodal.js b/app/addons/components/components/deletedatabasemodal.js
new file mode 100644
index 0000000..f24e553
--- /dev/null
+++ b/app/addons/components/components/deletedatabasemodal.js
@@ -0,0 +1,119 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+import {Modal} from "react-bootstrap";
+import Actions from "../actions";
+
+export const DeleteDatabaseModal = React.createClass({
+
+ getInitialState () {
+ return {
+ inputValue: '',
+ disableSubmit: true
+ };
+ },
+
+ propTypes: {
+ showHide: React.PropTypes.func.isRequired,
+ modalProps: React.PropTypes.object
+ },
+
+ close (e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ this.setState({
+ inputValue: '',
+ disableSubmit: true
+ });
+
+ this.props.showHide({showModal: false});
+ },
+
+ open () {
+ this.props.showHide({showModal: true});
+ },
+
+ getDatabaseName () {
+ return this.props.modalProps.dbId.trim();
+ },
+
+ onInputChange (e) {
+ const val = e.target.value.trim();
+
+ this.setState({
+ inputValue: val
+ });
+
+ this.setState({
+ disableSubmit: val !== this.getDatabaseName()
+ });
+ },
+
+ onDeleteClick (e) {
+ e.preventDefault();
+
+ Actions.deleteDatabase(this.getDatabaseName());
+ },
+
+ onInputKeypress (e) {
+ if (e.keyCode === 13 && this.state.disableSubmit !== true) {
+ Actions.deleteDatabase(this.getDatabaseName());
+ }
+ },
+
+ render () {
+ 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;
+
+ 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>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/document.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/document.js b/app/addons/components/components/document.js
new file mode 100644
index 0000000..1bf6ced
--- /dev/null
+++ b/app/addons/components/components/document.js
@@ -0,0 +1,126 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+import FauxtonAPI from "../../../core/api";
+import Helpers from "../../documents/helpers";
+
+export const Document = React.createClass({
+ propTypes: {
+ docIdentifier: React.PropTypes.string.isRequired,
+ docChecked: React.PropTypes.func.isRequired,
+ truncate: React.PropTypes.bool,
+ maxRows: React.PropTypes.number
+ },
+
+ getDefaultProps () {
+ return {
+ truncate: true,
+ maxRows: 500
+ };
+ },
+
+ onChange (e) {
+ e.preventDefault();
+ this.props.docChecked(this.props.doc.id, this.props.doc._rev);
+ },
+
+ getUrlFragment () {
+ if (!this.props.children) {
+ return '';
+ }
+
+ return (
+ <div className="doc-edit-symbol pull-right" title="Edit document">
+ {this.props.children}
+ </div>
+ );
+ },
+
+ getExtensionIcons () {
+ var extensions = FauxtonAPI.getExtensions('DocList:icons');
+ return _.map(extensions, function (Extension, i) {
+ return (<Extension doc={this.props.doc} key={i} />);
+ }, this);
+ },
+
+ getCheckbox () {
+ 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>
+ );
+ },
+
+ onDoubleClick (e) {
+ this.props.onDoubleClick(this.props.docIdentifier, this.props.doc, e);
+ },
+
+ getDocContent () {
+ 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;
+ }
+
+ return (
+ <div className="doc-data">
+ <pre className="prettyprint">{content}</pre>
+ {isTruncated ? <div className="doc-content-truncated">(truncated)</div> : null}
+ </div>
+ );
+ },
+
+ render () {
+ 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>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/loadlines.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/loadlines.js b/app/addons/components/components/loadlines.js
new file mode 100644
index 0000000..0fa2b74
--- /dev/null
+++ b/app/addons/components/components/loadlines.js
@@ -0,0 +1,21 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+
+export const LoadLines = () =>
+ <div className="loading-lines">
+ <div id="line1"> </div>
+ <div id="line2"> </div>
+ <div id="line3"> </div>
+ <div id="line4"> </div>
+ </div>;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/menudropdown.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/menudropdown.js b/app/addons/components/components/menudropdown.js
new file mode 100644
index 0000000..18127a1
--- /dev/null
+++ b/app/addons/components/components/menudropdown.js
@@ -0,0 +1,82 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+
+export const MenuDropDown = React.createClass({
+
+ getDefaultProps () {
+ return {
+ icon: 'fonticon-plus-circled'
+ };
+ },
+
+ createSectionLinks (links) {
+ if (!links) { return null; }
+
+ return links.map((link, key) => {
+ return this.createEntry(link, key);
+ });
+ },
+
+ createEntry (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 (title) {
+ if (!title) {
+ return null;
+ }
+
+ return (
+ <li className="header-label">{title}</li>
+ );
+ },
+
+ createSection () {
+ return this.props.links.map((linkSection, key) => {
+ if (linkSection.title && linkSection.links) {
+ return ([
+ this.createSectionTitle(linkSection.title),
+ this.createSectionLinks(linkSection.links)
+ ]);
+ }
+
+ return this.createEntry(linkSection, 'el' + key);
+
+ });
+ },
+
+ render () {
+ 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>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/paddedborderbox.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/paddedborderbox.js b/app/addons/components/components/paddedborderbox.js
new file mode 100644
index 0000000..22541fd
--- /dev/null
+++ b/app/addons/components/components/paddedborderbox.js
@@ -0,0 +1,25 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+
+export const PaddedBorderedBox = React.createClass({
+ render: function () {
+ return (
+ <div className="bordered-box">
+ <div className="padded-box">
+ {this.props.children}
+ </div>
+ </div>
+ );
+ }
+ });
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/stringeditmodal.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/stringeditmodal.js b/app/addons/components/components/stringeditmodal.js
new file mode 100644
index 0000000..782a3db
--- /dev/null
+++ b/app/addons/components/components/stringeditmodal.js
@@ -0,0 +1,95 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+import {Modal} from "react-bootstrap";
+import ace from "brace";
+import Helpers from "../../documents/helpers";
+require('brace/mode/javascript');
+require('brace/mode/json');
+require('brace/theme/idle_fingers');
+
+// this appears when the cursor is over a string. It shows an icon in the gutter that opens the modal.
+export const 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 () {
+ return {
+ visible: false,
+ onClose () { },
+ onSave () { }
+ };
+ },
+
+ componentDidMount () {
+ if (!this.props.visible) {
+ return;
+ }
+ this.initEditor(this.props.value);
+ },
+
+ componentDidUpdate (prevProps) {
+ if (!this.props.visible) {
+ return;
+ }
+ var val = '';
+ if (!prevProps.visible && this.props.visible) {
+ val = Helpers.parseJSON(this.props.value);
+ }
+
+ this.initEditor(val);
+ },
+
+ initEditor (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 () {
+ this.props.onClose();
+ },
+
+ save () {
+ this.props.onSave(this.editor.getValue());
+ },
+
+ render () {
+ return (
+ <Modal dialogClassName="string-editor-modal" show={this.props.visible} onHide={this.closeModal}>
+ <Modal.Header closeButton={true}>
+ <Modal.Title>Edit Value <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>
+ <a className="cancel-link" onClick={this.closeModal}>Cancel</a>
+ <button id="string-edit-save-btn" onClick={this.save} className="btn btn-success save">
+ <i className="fonticon-circle-check"></i> Modify Text
+ </button>
+ </Modal.Footer>
+ </Modal>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/styledselect.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/styledselect.js b/app/addons/components/components/styledselect.js
new file mode 100644
index 0000000..85a6ff4
--- /dev/null
+++ b/app/addons/components/components/styledselect.js
@@ -0,0 +1,39 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+
+export const StyledSelect = React.createClass({
+ propTypes: {
+ selectValue: React.PropTypes.string.isRequired,
+ selectId: React.PropTypes.string.isRequired,
+ selectChange: React.PropTypes.func.isRequired
+ },
+
+ 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>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/tabelement.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/tabelement.js b/app/addons/components/components/tabelement.js
new file mode 100644
index 0000000..0946597
--- /dev/null
+++ b/app/addons/components/components/tabelement.js
@@ -0,0 +1,55 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+
+export 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,
+};
+
+export const TabElementWrapper = ({children}) => {
+ return (
+ <ul className="component-tab-element-wrapper">
+ {children}
+ </ul>
+ );
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/toggleheaderbutton.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/toggleheaderbutton.js b/app/addons/components/components/toggleheaderbutton.js
new file mode 100644
index 0000000..782c369
--- /dev/null
+++ b/app/addons/components/components/toggleheaderbutton.js
@@ -0,0 +1,46 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+
+export const ToggleHeaderButton = React.createClass({
+ getDefaultProps () {
+ return {
+ innerClasses: '',
+ fonticon: '',
+ containerClasses: '',
+ selected: false,
+ title: '',
+ disabled: false,
+ toggleCallback: null,
+ text: '',
+ iconDefaultClass: 'icon'
+ };
+ },
+
+ render () {
+ const { iconDefaultClass, fonticon, innerClasses, selected, containerClasses, title, disabled, text, toggleCallback } = this.props;
+ const selectedBtnClass = (selected) ? 'js-headerbar-togglebutton-selected' : '';
+
+ return (
+ <button
+ title={title}
+ disabled={disabled}
+ onClick={toggleCallback}
+ className={`button ${containerClasses} ${selectedBtnClass}`}
+ >
+ <i className={`${iconDefaultClass} ${fonticon} ${innerClasses}`}></i><span>{text}</span>
+ </button>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/tray.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/tray.js b/app/addons/components/components/tray.js
new file mode 100644
index 0000000..6fc8f86
--- /dev/null
+++ b/app/addons/components/components/tray.js
@@ -0,0 +1,108 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from "react";
+import ReactDOM from "react-dom";
+import ReactCSSTransitionGroup from "react-addons-css-transition-group";
+
+export const TrayContents = React.createClass({
+ getChildren () {
+ var className = "tray show-tray " + this.props.className;
+ return (
+ <div key={1} id={this.props.id} className={className}>
+ {this.props.children}
+ </div>);
+ },
+
+ render () {
+ return (
+ <ReactCSSTransitionGroup transitionName="tray" transitionAppear={true} component="div" transitionAppearTimeout={500}
+ transitionEnterTimeout={500} transitionLeaveTimeout={300}>
+ {this.getChildren()}
+ </ReactCSSTransitionGroup>
+ );
+ }
+});
+
+
+export const connectToStores = (Component, stores, getStateFromStores) => {
+
+ var WrappingElement = React.createClass({
+
+ componentDidMount () {
+ stores.forEach(function (store) {
+ store.on('change', this.onChange, this);
+ }.bind(this));
+ },
+
+ componentWillUnmount () {
+ stores.forEach(function (store) {
+ store.off('change', this.onChange);
+ }.bind(this));
+ },
+
+ getInitialState () {
+ return getStateFromStores(this.props);
+ },
+
+ onChange () {
+ if (!this.isMounted()) {
+ return;
+ }
+
+ this.setState(getStateFromStores(this.props));
+ },
+
+ handleStoresChanged () {
+ if (this.isMounted()) {
+ this.setState(getStateFromStores(this.props));
+ }
+ },
+
+ render () {
+ return <Component {...this.state} {...this.props} />;
+ }
+
+ });
+
+ return WrappingElement;
+};
+
+export const TrayWrapper = React.createClass({
+ getDefaultProps () {
+ return {
+ className: ''
+ };
+ },
+
+ renderChildren () {
+ return React.Children.map(this.props.children, function (child, key) {
+
+ const props = {};
+ Object.keys(this.props).filter((k) => {
+ return this.props.hasOwnProperty(k);
+ }).map((k) => {
+ return props[k] = this.props[k];
+ });
+
+ return React.cloneElement(child, props);
+ }.bind(this));
+ },
+
+ render () {
+ return (
+ <div>
+ {this.renderChildren()}
+ </div>
+ );
+ }
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/369c3265/app/addons/components/components/zenmodeoverlay.js
----------------------------------------------------------------------
diff --git a/app/addons/components/components/zenmodeoverlay.js b/app/addons/components/components/zenmodeoverlay.js
new file mode 100644
index 0000000..8490d92
--- /dev/null
+++ b/app/addons/components/components/zenmodeoverlay.js
@@ -0,0 +1,131 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import React from "react";
+import ReactDOM from "react-dom";
+import app from "../../../app";
+import {CodeEditor} from './codeeditor';
+
+require('brace/theme/dawn');
+
+// 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
+export const ZenModeOverlay = React.createClass({
+ getDefaultProps () {
+ return {
+ mode: 'javascript',
+ defaultCode: '',
+ ignorableErrors: [],
+ onExit: null,
+ highlightActiveLine: false
+ };
+ },
+
+ themes: {
+ dark: 'idle_fingers',
+ light: 'dawn'
+ },
+
+ getInitialState () {
+ return this.getStoreState();
+ },
+
+ getStoreState () {
+ return {
+ theme: this.getZenTheme(),
+ code: this.props.defaultCode
+ };
+ },
+
+ getZenTheme () {
+ var selectedTheme = app.utils.localStorageGet('zenTheme');
+ return _.isUndefined(selectedTheme) ? 'dark' : selectedTheme;
+ },
+
+ onChange () {
+ this.setState(this.getStoreState());
+ },
+
+ componentDidMount () {
+ $(ReactDOM.findDOMNode(this.refs.exit)).tooltip({ placement: 'left' });
+ $(ReactDOM.findDOMNode(this.refs.theme)).tooltip({ placement: 'left' });
+ },
+
+ exitZenMode () {
+ this.props.onExit(this.getValue());
+ },
+
+ getValue () {
+ return this.refs.ace.getValue();
+ },
+
+ toggleTheme () {
+ var newTheme = (this.state.theme === 'dark') ? 'light' : 'dark';
+ this.setState({
+ theme: newTheme,
+ code: this.getValue()
+ });
+ app.utils.localStorageSet('zenTheme', newTheme);
+ },
+
+ setValue (code, lineNumber) {
+ lineNumber = lineNumber ? lineNumber : -1;
+ this.editor.setValue(code, lineNumber);
+ },
+
+ render () {
+ 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 (
+ <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>
+ );
+ }
+});