[web] header.js -> Header.js

This commit is contained in:
Jason 2016-06-09 20:34:57 +08:00
parent 6c95635cb8
commit 81a0c45c89
14 changed files with 61937 additions and 803 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@ function EventLog({ filters, events, onToggleFilter, onClose }) {
Eventlog
<div className="pull-right">
{['debug', 'info', 'web'].map(type => (
<ToggleButton text={type} checked={filters[type]} onToggle={() => onToggleFilter(type)}/>
<ToggleButton key={type} text={type} checked={filters[type]} onToggle={() => onToggleFilter(type)}/>
))}
<i onClick={onClose} className="fa fa-close"></i>
</div>

View File

@ -0,0 +1,56 @@
import React, { Component, PropTypes } from 'react'
import classnames from 'classnames'
import { toggleEventLogVisibility } from '../ducks/eventLog'
import MainMenu from './Header/MainMenu'
import ViewMenu from './Header/ViewMenu'
import OptionMenu from './Header/OptionMenu'
import FileMenu from './Header/FileMenu'
export default class Header extends Component {
static entries = [MainMenu, ViewMenu, OptionMenu]
static propTypes = {
settings: PropTypes.object.isRequired,
}
constructor(props, context) {
super(props, context)
this.state = { active: Header.entries[0] }
}
handleClick(active, e) {
e.preventDefault()
this.props.updateLocation(active.route)
this.setState({ active })
}
render() {
const { active: Active } = this.state
const { settings, updateLocation, query } = this.props
return (
<header>
<nav className="nav-tabs nav-tabs-lg">
<FileMenu/>
{Header.entries.map(Entry => (
<a key={Entry.title}
href="#"
className={classnames({ active: Entry === Active })}
onClick={e => this.handleClick(Entry, e)}>
{Entry.title}
</a>
))}
</nav>
<div className="menu">
<Active
ref="active"
settings={settings}
updateLocation={updateLocation}
query={query}
/>
</div>
</header>
)
}
}

View File

@ -0,0 +1,100 @@
import React, { Component } from 'react'
import classnames from 'classnames'
import { FlowActions } from '../../actions.js'
export default class FileMenu extends Component {
constructor(props, context) {
super(props, context)
this.state = { show: false }
this.close = this.close.bind(this)
this.onFileClick = this.onFileClick.bind(this)
this.onNewClick = this.onNewClick.bind(this)
this.onOpenClick = this.onOpenClick.bind(this)
this.onOpenFile = this.onOpenFile.bind(this)
this.onSaveClick = this.onSaveClick.bind(this)
}
close() {
this.setState({ show: false })
document.removeEventListener('click', this.close)
}
onFileClick(e) {
e.preventDefault()
if (this.state.show) {
return
}
document.addEventListener('click', this.close)
this.setState({ show: true })
}
onNewClick(e) {
e.preventDefault()
if (confirm('Delete all flows?')) {
FlowActions.clear()
}
}
onOpenClick(e) {
e.preventDefault()
this.fileInput.click()
}
onOpenFile(e) {
e.preventDefault()
if (e.target.files.length > 0) {
FlowActions.upload(e.target.files[0])
this.fileInput.value = ''
}
}
onSaveClick(e) {
e.preventDefault()
FlowActions.download()
}
render() {
return (
<div className={classnames('dropdown pull-left', { open: this.state.show })}>
<a href="#" className="special" onClick={this.onFileClick}>mitmproxy</a>
<ul className="dropdown-menu" role="menu">
<li>
<a href="#" onClick={this.onNewClick}>
<i className="fa fa-fw fa-file"></i>
New
</a>
</li>
<li>
<a href="#" onClick={this.onOpenClick}>
<i className="fa fa-fw fa-folder-open"></i>
Open...
</a>
<input
ref={ref => this.fileInput = ref}
className="hidden"
type="file"
onChange={this.onOpenFile}
/>
</li>
<li>
<a href="#" onClick={this.onSaveClick}>
<i className="fa fa-fw fa-floppy-o"></i>
Save...
</a>
</li>
<li role="presentation" className="divider"></li>
<li>
<a href="http://mitm.it/" target="_blank">
<i className="fa fa-fw fa-external-link"></i>
Install Certificates...
</a>
</li>
</ul>
</div>
)
}
}

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react'
import $ from 'jquery'
export default class FilterDocs extends Component {
// @todo move to redux
static xhr = null
static doc = null
constructor(props, context) {
super(props, context)
this.state = { doc: FilterDocs.doc }
}
componentWillMount() {
if (!FilterDocs.xhr) {
FilterDocs.xhr = $.getJSON('/filter-help')
FilterDocs.xhr.fail(() => {
FilterDocs.xhr = null
})
}
if (!this.state.doc) {
FilterDocs.xhr.done(doc => {
FilterDocs.doc = doc
this.setState({ doc })
})
}
}
render() {
const { doc } = this.state
return !doc ? (
<i className="fa fa-spinner fa-spin"></i>
) : (
<table className="table table-condensed">
<tbody>
{doc.commands.map(cmd => (
<tr key={cmd[1]}>
<td>{cmd[0].replace(' ', '\u00a0')}</td>
<td>{cmd[1]}</td>
</tr>
))}
<tr key="docs-link">
<td colSpan="2">
<a href="http://docs.mitmproxy.org/en/stable/features/filters.html"
target="_blank">
<i className="fa fa-external-link"></i>
&nbsp mitmproxy docs</a>
</td>
</tr>
</tbody>
</table>
)
}
}

View File

@ -0,0 +1,133 @@
import React, { PropTypes, Component } from 'react'
import ReactDOM from 'react-dom'
import classnames from 'classnames'
import { Key } from '../../utils.js'
import Filt from '../../filt/filt'
import FilterDocs from './FilterDocs'
export default class FilterInput extends Component {
static contextTypes = {
returnFocus: React.PropTypes.func,
}
constructor(props, context) {
super(props, context)
// Consider both focus and mouseover for showing/hiding the tooltip,
// because onBlur of the input is triggered before the click on the tooltip
// finalized, hiding the tooltip just as the user clicks on it.
this.state = { value: this.props.value, focus: false, mousefocus: false }
this.onChange = this.onChange.bind(this)
this.onFocus = this.onFocus.bind(this)
this.onBlur = this.onBlur.bind(this)
this.onKeyDown = this.onKeyDown.bind(this)
this.onMouseEnter = this.onMouseEnter.bind(this)
this.onMouseLeave = this.onMouseLeave.bind(this)
}
componentWillReceiveProps(nextProps) {
this.setState({ value: nextProps.value })
}
isValid(filt) {
try {
const str = filt == null ? this.state.value : filt
if (str) {
Filt.parse(str)
}
return true
} catch (e) {
return false
}
}
getDesc() {
if (!this.state.value) {
return <FilterDocs/>
}
try {
return Filt.parse(this.state.value).desc
} catch (e) {
return '' + e
}
}
onChange(e) {
const value = e.target.value
this.setState({ value })
// Only propagate valid filters upwards.
if (this.isValid(value)) {
this.props.onChange(value)
}
}
onFocus() {
this.setState({ focus: true })
}
onBlur() {
this.setState({ focus: false })
}
onMouseEnter() {
this.setState({ mousefocus: true })
}
onMouseLeave() {
this.setState({ mousefocus: false })
}
onKeyDown(e) {
if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) {
this.blur()
// If closed using ESC/ENTER, hide the tooltip.
this.setState({mousefocus: false})
}
e.stopPropagation()
}
blur() {
ReactDOM.findDOMNode(this.refs.input).blur()
this.context.returnFocus()
}
select() {
ReactDOM.findDOMNode(this.refs.input).select()
}
render() {
const { type, color, placeholder } = this.props
const { value, focus, mousefocus } = this.state
return (
<div className={classnames('filter-input input-group', { 'has-error': !this.isValid() })}>
<span className="input-group-addon">
<i className={'fa fa-fw fa-' + type} style={{ color }}></i>
</span>
<input
type="text"
ref="input"
placeholder={placeholder}
className="form-control"
value={value}
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
/>
{(focus || mousefocus) && (
<div className="popover bottom"
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}>
<div className="arrow"></div>
<div className="popover-content">
{this.getDesc()}
</div>
</div>
)}
</div>
)
}
}

View File

@ -0,0 +1,73 @@
import React, { Component, PropTypes } from 'react'
import { SettingsActions } from "../../actions.js"
import FilterInput from './FilterInput'
import { Query } from '../../actions.js'
export default class MainMenu extends Component {
static title = 'Start'
static route = 'flows'
static propTypes = {
settings: React.PropTypes.object.isRequired,
}
constructor(props, context) {
super(props, context)
this.onSearchChange = this.onSearchChange.bind(this)
this.onHighlightChange = this.onHighlightChange.bind(this)
this.onInterceptChange = this.onInterceptChange.bind(this)
}
onSearchChange(val) {
this.props.updateLocation(undefined, { [Query.SEARCH]: val })
}
onHighlightChange(val) {
this.props.updateLocation(undefined, { [Query.HIGHLIGHT]: val })
}
onInterceptChange(val) {
SettingsActions.update({ intercept: val })
}
render() {
const { query, settings } = this.props
const search = query[Query.SEARCH] || ''
const highlight = query[Query.HIGHLIGHT] || ''
const intercept = settings.intercept || ''
return (
<div>
<div className="menu-row">
<FilterInput
ref="search"
placeholder="Search"
type="search"
color="black"
value={search}
onChange={this.onSearchChange}
/>
<FilterInput
ref="highlight"
placeholder="Highlight"
type="tag"
color="hsl(48, 100%, 50%)"
value={highlight}
onChange={this.onHighlightChange}
/>
<FilterInput
ref="intercept"
placeholder="Intercept"
type="pause"
color="hsl(208, 56%, 53%)"
value={intercept}
onChange={this.onInterceptChange}
/>
</div>
<div className="clearfix"></div>
</div>
)
}
}

View File

@ -0,0 +1,60 @@
import React, { PropTypes } from 'react'
import { ToggleInputButton, ToggleButton } from '../common.js'
import { SettingsActions } from '../../actions.js'
OptionMenu.title = "Options"
OptionMenu.propTypes = {
settings: PropTypes.object.isRequired,
}
export default function OptionMenu({ settings }) {
// @todo use settings.map
return (
<div>
<div className="menu-row">
<ToggleButton text="showhost"
checked={settings.showhost}
onToggle={() => SettingsActions.update({ showhost: !settings.showhost })}
/>
<ToggleButton text="no_upstream_cert"
checked={settings.no_upstream_cert}
onToggle={() => SettingsActions.update({ no_upstream_cert: !settings.no_upstream_cert })}
/>
<ToggleButton text="rawtcp"
checked={settings.rawtcp}
onToggle={() => SettingsActions.update({ rawtcp: !settings.rawtcp })}
/>
<ToggleButton text="http2"
checked={settings.http2}
onToggle={() => SettingsActions.update({ http2: !settings.http2 })}
/>
<ToggleButton text="anticache"
checked={settings.anticache}
onToggle={() => SettingsActions.update({ anticache: !settings.anticache })}
/>
<ToggleButton text="anticomp"
checked={settings.anticomp}
onToggle={() => SettingsActions.update({ anticomp: !settings.anticomp })}
/>
<ToggleInputButton name="stickyauth" placeholder="Sticky auth filter"
checked={!!settings.stickyauth}
txt={settings.stickyauth || ''}
onToggleChanged={txt => SettingsActions.update({ stickyauth: !settings.stickyauth ? txt : null })}
/>
<ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter"
checked={!!settings.stickycookie}
txt={settings.stickycookie || ''}
onToggleChanged={txt => SettingsActions.update({ stickycookie: !settings.stickycookie ? txt : null })}
/>
<ToggleInputButton name="stream" placeholder="stream..."
checked={!!settings.stream}
txt={settings.stream || ''}
inputType="number"
onToggleChanged={txt => SettingsActions.update({ stream: !settings.stream ? txt : null })}
/>
</div>
<div className="clearfix"/>
</div>
)
}

View File

@ -0,0 +1,33 @@
import React, { PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { ToggleButton } from '../common.js'
import { toggleEventLogVisibility } from '../../ducks/eventLog'
ViewMenu.title = 'View'
ViewMenu.route = 'flows'
ViewMenu.propTypes = {
visible: PropTypes.bool.isRequired,
onToggle: PropTypes.func.isRequired,
}
function ViewMenu({ visible, onToggle }) {
return (
<div>
<div className="menu-row">
<ToggleButton text="Show Event Log" checked={visible} onToggle={onToggle} />
</div>
<div className="clearfix"></div>
</div>
)
}
export default connect(
state => ({
visible: state.eventLog.visible,
}),
dispatch => bindActionCreators({
onToggle: toggleEventLogVisibility,
}, dispatch)
)(ViewMenu)

View File

@ -4,7 +4,7 @@ import _ from "lodash"
import { connect } from 'react-redux'
import { Splitter } from "./common.js"
import { Header, MainMenu } from "./header.js"
import Header from "./Header"
import EventLog from "./EventLog"
import Footer from "./Footer"
import { SettingsStore } from "../store/store.js"
@ -134,7 +134,7 @@ class ProxyAppMain extends Component {
if (name) {
const headerComponent = this.refs.header
headerComponent.setState({ active: MainMenu }, () => {
headerComponent.setState({ active: Header.entries.MainMenu }, () => {
headerComponent.refs.active.refs[name].select()
})
}

View File

@ -1,464 +0,0 @@
import React from "react";
import ReactDOM from 'react-dom';
import { bindActionCreators } from 'redux'
import $ from "jquery";
import {connect} from 'react-redux'
import Filt from "../filt/filt.js";
import {Key} from "../utils.js";
import {ToggleInputButton, ToggleButton} from "./common.js";
import {SettingsActions, FlowActions} from "../actions.js";
import {Query} from "../actions.js";
import {SettingsState} from "./common.js";
import { toggleEventLogVisibility } from '../ducks/eventLog'
const ToggleEventLog = connect(
state => ({
checked: state.eventLog.visible
}),
dispatch => bindActionCreators({
onToggle: toggleEventLogVisibility,
}, dispatch)
)(ToggleButton)
var FilterDocs = React.createClass({
statics: {
xhr: false,
doc: false
},
componentWillMount: function () {
if (!FilterDocs.doc) {
FilterDocs.xhr = $.getJSON("/filter-help").done(function (doc) {
FilterDocs.doc = doc;
FilterDocs.xhr = false;
});
}
if (FilterDocs.xhr) {
FilterDocs.xhr.done(function () {
this.forceUpdate();
}.bind(this));
}
},
render: function () {
if (!FilterDocs.doc) {
return <i className="fa fa-spinner fa-spin"></i>;
} else {
var commands = FilterDocs.doc.commands.map(function (c) {
return <tr key={c[1]}>
<td>{c[0].replace(" ", '\u00a0')}</td>
<td>{c[1]}</td>
</tr>;
});
commands.push(<tr key="docs-link">
<td colSpan="2">
<a href="http://docs.mitmproxy.org/en/stable/features/filters.html"
target="_blank">
<i className="fa fa-external-link"></i>
&nbsp; mitmproxy docs</a>
</td>
</tr>);
return <table className="table table-condensed">
<tbody>{commands}</tbody>
</table>;
}
}
});
var FilterInput = React.createClass({
contextTypes: {
returnFocus: React.PropTypes.func
},
getInitialState: function () {
// Consider both focus and mouseover for showing/hiding the tooltip,
// because onBlur of the input is triggered before the click on the tooltip
// finalized, hiding the tooltip just as the user clicks on it.
return {
value: this.props.value,
focus: false,
mousefocus: false
};
},
componentWillReceiveProps: function (nextProps) {
this.setState({value: nextProps.value});
},
onChange: function (e) {
var nextValue = e.target.value;
this.setState({
value: nextValue
});
// Only propagate valid filters upwards.
if (this.isValid(nextValue)) {
this.props.onChange(nextValue);
}
},
isValid: function (filt) {
try {
var str = filt || this.state.value;
if(str){
Filt.parse(filt || this.state.value);
}
return true;
} catch (e) {
return false;
}
},
getDesc: function () {
if(this.state.value) {
try {
return Filt.parse(this.state.value).desc;
} catch (e) {
return "" + e;
}
}
return <FilterDocs/>;
},
onFocus: function () {
this.setState({focus: true});
},
onBlur: function () {
this.setState({focus: false});
},
onMouseEnter: function () {
this.setState({mousefocus: true});
},
onMouseLeave: function () {
this.setState({mousefocus: false});
},
onKeyDown: function (e) {
if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) {
this.blur();
// If closed using ESC/ENTER, hide the tooltip.
this.setState({mousefocus: false});
}
e.stopPropagation();
},
blur: function () {
ReactDOM.findDOMNode(this.refs.input).blur();
this.context.returnFocus();
},
select: function () {
ReactDOM.findDOMNode(this.refs.input).select();
},
render: function () {
var isValid = this.isValid();
var icon = "fa fa-fw fa-" + this.props.type;
var groupClassName = "filter-input input-group" + (isValid ? "" : " has-error");
var popover;
if (this.state.focus || this.state.mousefocus) {
popover = (
<div className="popover bottom" onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<div className="arrow"></div>
<div className="popover-content">
{this.getDesc()}
</div>
</div>
);
}
return (
<div className={groupClassName}>
<span className="input-group-addon">
<i className={icon} style={{color: this.props.color}}></i>
</span>
<input type="text" placeholder={this.props.placeholder} className="form-control"
ref="input"
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
value={this.state.value}/>
{popover}
</div>
);
}
});
export var MainMenu = React.createClass({
propTypes: {
settings: React.PropTypes.object.isRequired,
},
statics: {
title: "Start",
route: "flows"
},
onSearchChange: function (val) {
var d = {};
d[Query.SEARCH] = val;
this.props.updateLocation(undefined, d);
},
onHighlightChange: function (val) {
var d = {};
d[Query.HIGHLIGHT] = val;
this.props.updateLocation(undefined, d);
},
onInterceptChange: function (val) {
SettingsActions.update({intercept: val});
},
render: function () {
var search = this.props.query[Query.SEARCH] || "";
var highlight = this.props.query[Query.HIGHLIGHT] || "";
var intercept = this.props.settings.intercept || "";
return (
<div>
<div className="menu-row">
<FilterInput
ref="search"
placeholder="Search"
type="search"
color="black"
value={search}
onChange={this.onSearchChange} />
<FilterInput
ref="highlight"
placeholder="Highlight"
type="tag"
color="hsl(48, 100%, 50%)"
value={highlight}
onChange={this.onHighlightChange}/>
<FilterInput
ref="intercept"
placeholder="Intercept"
type="pause"
color="hsl(208, 56%, 53%)"
value={intercept}
onChange={this.onInterceptChange}/>
</div>
<div className="clearfix"></div>
</div>
);
}
});
var ViewMenu = React.createClass({
statics: {
title: "View",
route: "flows"
},
render: function () {
return (
<div>
<div className="menu-row">
<ToggleEventLog text="Show Event Log"/>
</div>
<div className="clearfix"></div>
</div>
);
}
});
export const OptionMenu = (props) => {
const {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickycookie, stickyauth, stream} = props.settings;
return (
<div>
<div className="menu-row">
<ToggleButton text="showhost"
checked={showhost}
onToggle={() => SettingsActions.update({showhost: !showhost})}
/>
<ToggleButton text="no_upstream_cert"
checked={no_upstream_cert}
onToggle={() => SettingsActions.update({no_upstream_cert: !no_upstream_cert})}
/>
<ToggleButton text="rawtcp"
checked={rawtcp}
onToggle={() => SettingsActions.update({rawtcp: !rawtcp})}
/>
<ToggleButton text="http2"
checked={http2}
onToggle={() => SettingsActions.update({http2: !http2})}
/>
<ToggleButton text="anticache"
checked={anticache}
onToggle={() => SettingsActions.update({anticache: !anticache})}
/>
<ToggleButton text="anticomp"
checked={anticomp}
onToggle={() => SettingsActions.update({anticomp: !anticomp})}
/>
<ToggleInputButton name="stickyauth" placeholder="Sticky auth filter"
checked={Boolean(stickyauth)}
txt={stickyauth || ""}
onToggleChanged={txt => SettingsActions.update({stickyauth: (!stickyauth ? txt : null)})}
/>
<ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter"
checked={Boolean(stickycookie)}
txt={stickycookie || ""}
onToggleChanged={txt => SettingsActions.update({stickycookie: (!stickycookie ? txt : null)})}
/>
<ToggleInputButton name="stream" placeholder="stream..."
checked={Boolean(stream)}
txt={stream || ""}
inputType = "number"
onToggleChanged={txt => SettingsActions.update({stream: (!stream ? txt : null)})}
/>
</div>
<div className="clearfix"/>
</div>
);
};
OptionMenu.title = "Options";
OptionMenu.propTypes = {
settings: React.PropTypes.object.isRequired
};
var ReportsMenu = React.createClass({
statics: {
title: "Visualization",
route: "reports"
},
render: function () {
return <div>Reports Menu</div>;
}
});
var FileMenu = React.createClass({
getInitialState: function () {
return {
showFileMenu: false
};
},
handleFileClick: function (e) {
e.preventDefault();
if (!this.state.showFileMenu) {
var close = function () {
this.setState({showFileMenu: false});
document.removeEventListener("click", close);
}.bind(this);
document.addEventListener("click", close);
this.setState({
showFileMenu: true
});
}
},
handleNewClick: function (e) {
e.preventDefault();
if (confirm("Delete all flows?")) {
FlowActions.clear();
}
},
handleOpenClick: function (e) {
this.fileInput.click();
e.preventDefault();
},
handleOpenFile: function (e) {
if (e.target.files.length > 0) {
FlowActions.upload(e.target.files[0]);
this.fileInput.value = "";
}
e.preventDefault();
},
handleSaveClick: function (e) {
e.preventDefault();
FlowActions.download();
},
handleShutdownClick: function (e) {
e.preventDefault();
console.error("unimplemented: handleShutdownClick");
},
render: function () {
var fileMenuClass = "dropdown pull-left" + (this.state.showFileMenu ? " open" : "");
return (
<div className={fileMenuClass}>
<a href="#" className="special" onClick={this.handleFileClick}> mitmproxy </a>
<ul className="dropdown-menu" role="menu">
<li>
<a href="#" onClick={this.handleNewClick}>
<i className="fa fa-fw fa-file"></i>
New
</a>
</li>
<li>
<a href="#" onClick={this.handleOpenClick}>
<i className="fa fa-fw fa-folder-open"></i>
Open...
</a>
<input ref={(ref) => this.fileInput = ref} className="hidden" type="file" onChange={this.handleOpenFile}/>
</li>
<li>
<a href="#" onClick={this.handleSaveClick}>
<i className="fa fa-fw fa-floppy-o"></i>
Save...
</a>
</li>
<li role="presentation" className="divider"></li>
<li>
<a href="http://mitm.it/" target="_blank">
<i className="fa fa-fw fa-external-link"></i>
Install Certificates...
</a>
</li>
{/*
<li role="presentation" className="divider"></li>
<li>
<a href="#" onClick={this.handleShutdownClick}>
<i className="fa fa-fw fa-plug"></i>
Shutdown
</a>
</li>
*/}
</ul>
</div>
);
}
});
var header_entries = [MainMenu, ViewMenu, OptionMenu /*, ReportsMenu */];
export var Header = React.createClass({
propTypes: {
settings: React.PropTypes.object.isRequired,
},
getInitialState: function () {
return {
active: header_entries[0]
};
},
handleClick: function (active, e) {
e.preventDefault();
this.props.updateLocation(active.route);
this.setState({active: active});
},
render: function () {
var header = header_entries.map(function (entry, i) {
var className;
if (entry === this.state.active) {
className = "active";
} else {
className = "";
}
return (
<a key={i}
href="#"
className={className}
onClick={this.handleClick.bind(this, entry)}>
{entry.title}
</a>
);
}.bind(this));
return (
<header>
<nav className="nav-tabs nav-tabs-lg">
<FileMenu/>
{header}
</nav>
<div className="menu">
<this.state.active
ref="active"
settings={this.props.settings}
updateLocation={this.props.updateLocation}
query={this.props.query}
/>
</div>
</header>
);
}
});

View File

@ -42,7 +42,7 @@ var Prompt = React.createClass({
var opts = [];
var keyTaken = function (k) {
return _.includes(_.pluck(opts, "key"), k);
return _.includes(_.map(opts, "key"), k);
};
for (var i = 0; i < this.props.options.length; i++) {
@ -99,4 +99,4 @@ var Prompt = React.createClass({
}
});
export default Prompt;
export default Prompt;