diff options
author | Maximilian Hils <git@maximilianhils.com> | 2015-05-01 17:24:44 +0200 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2015-05-01 17:24:44 +0200 |
commit | 3f5ca10c39a9f7d55e0f6943caf8f6ff762a0222 (patch) | |
tree | 03f697b75b1fc787573feed3df26bde446ff2319 /web/src | |
parent | 90dff4a8a15580cf3e86d29c6aba1f97410a0b89 (diff) | |
download | mitmproxy-3f5ca10c39a9f7d55e0f6943caf8f6ff762a0222.tar.gz mitmproxy-3f5ca10c39a9f7d55e0f6943caf8f6ff762a0222.tar.bz2 mitmproxy-3f5ca10c39a9f7d55e0f6943caf8f6ff762a0222.zip |
mitmweb: add editor
Diffstat (limited to 'web/src')
-rw-r--r-- | web/src/js/components/common.js | 5 | ||||
-rw-r--r-- | web/src/js/components/editor.js | 189 | ||||
-rw-r--r-- | web/src/js/components/flowview/messages.js | 196 | ||||
-rw-r--r-- | web/src/js/components/header.js | 2 | ||||
-rw-r--r-- | web/src/js/components/prompt.js | 2 |
5 files changed, 239 insertions, 155 deletions
diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js index b0aa0977..965ae9a7 100644 --- a/web/src/js/components/common.js +++ b/web/src/js/components/common.js @@ -55,6 +55,11 @@ var SettingsState = { var ChildFocus = { contextTypes: { returnFocus: React.PropTypes.func + }, + returnFocus: function(){ + React.findDOMNode(this).blur(); + window.getSelection().removeAllRanges(); + this.context.returnFocus(); } }; diff --git a/web/src/js/components/editor.js b/web/src/js/components/editor.js new file mode 100644 index 00000000..714a8e2a --- /dev/null +++ b/web/src/js/components/editor.js @@ -0,0 +1,189 @@ +var React = require("react"); +var common = require("./common.js"); +var utils = require("../utils.js"); + +var contentToHtml = function (content) { + return _.escape(content); +}; +var nodeToContent = function (node) { + return node.textContent; +}; + +/* +Basic Editor Functionality + */ +var EditorBase = React.createClass({ + propTypes: { + content: React.PropTypes.string.isRequired, + onDone: React.PropTypes.func.isRequired, + contentToHtml: React.PropTypes.func, + nodeToContent: React.PropTypes.func, // content === nodeToContent( Node<innerHTML=contentToHtml(content)> ) + submitOnEnter: React.PropTypes.bool, + className: React.PropTypes.string, + tag: React.PropTypes.string + }, + getDefaultProps: function () { + return { + contentToHtml: contentToHtml, + nodeToContent: nodeToContent, + submitOnEnter: true, + className: "", + tag: "div" + }; + }, + getInitialState: function () { + return { + editable: false + }; + }, + render: function () { + var className = "inline-input " + this.props.className; + var html = {__html: this.props.contentToHtml(this.props.content)}; + var Tag = this.props.tag; + return <Tag + {...this.props} + tabIndex="0" + className={className} + contentEditable={this.state.editable || undefined } // workaround: use undef instead of false to remove attr + onFocus={this.onFocus} + onBlur={this._stop} + onKeyDown={this.onKeyDown} + onInput={this.onInput} + dangerouslySetInnerHTML={html} + />; + }, + onFocus: function (e) { + this.setState({editable: true}, function () { + React.findDOMNode(this).focus(); + var range = document.createRange(); + range.selectNodeContents(this.getDOMNode()); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + }); + this.props.onFocus && this.props.onFocus(e); + }, + stop: function () { + // a stop would cause a blur as a side-effect. + // but a blur event must trigger a stop as well. + // to fix this, make stop = blur and do the actual stop in the onBlur handler. + React.findDOMNode(this).blur(); + }, + _stop: function (e) { + window.getSelection().removeAllRanges(); //make sure that selection is cleared on blur + var node = React.findDOMNode(this); + var content = this.props.nodeToContent(node); + this.setState({editable: false}); + this.props.onDone(content); + this.props.onBlur && this.props.onBlur(e); + }, + cancel: function () { + React.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content); + this.stop(); + }, + onKeyDown: function (e) { + e.stopPropagation(); + switch (e.keyCode) { + case utils.Key.ESC: + e.preventDefault(); + this.cancel(); + break; + case utils.Key.ENTER: + if (this.props.submitOnEnter) { + e.preventDefault(); + this.stop(); + } + break; + default: + break; + } + }, + onInput: function () { + var node = React.findDOMNode(this); + var content = this.props.nodeToContent(node); + node.innerHTML = this.props.contentToHtml(content); + this.props.onInput && this.props.onInput(content); + } +}); + +/* +Add Validation to EditorBase + */ +var ValidateEditor = React.createClass({ + propTypes: { + content: React.PropTypes.string.isRequired, + onDone: React.PropTypes.func.isRequired, + onInput: React.PropTypes.func, + isValid: React.PropTypes.func, + className: React.PropTypes.string, + }, + getInitialState: function(){ + return { + currentContent: this.props.content + }; + }, + componentWillReceiveProps: function(){ + this.setState({currentContent: this.props.content}); + }, + onInput: function(content){ + this.setState({currentContent: content}); + this.props.onInput && this.props.onInput(content); + }, + render: function () { + var className = this.props.className || ""; + if (this.props.isValid) { + if (this.props.isValid(this.state.currentContent)) { + className += " has-success"; + } else { + className += " has-warning" + } + } + return <EditorBase + {...this.props} + ref="editor" + className={className} + onDone={this.onDone} + onInput={this.onInput} + />; + }, + onDone: function (content) { + if(this.props.isValid && !this.props.isValid(content)){ + this.refs.editor.cancel(); + content = this.props.content; + } + this.props.onDone(content); + } +}); + +/* +Text Editor with mitmweb-specific convenience features + */ +var ValueEditor = React.createClass({ + mixins: [common.ChildFocus], + propTypes: { + content: React.PropTypes.string.isRequired, + onDone: React.PropTypes.func.isRequired, + inline: React.PropTypes.bool, + }, + render: function () { + var tag = this.props.inline ? "span" : "div"; + return <ValidateEditor + {...this.props} + onBlur={this.onBlur} + tag={tag} + />; + }, + focus: function () { + React.findDOMNode(this).focus(); + }, + onBlur: function(e){ + if(!e.relatedTarget){ + this.returnFocus(); + } + this.props.onBlur && this.props.onBlur(e); + } +}); + +module.exports = { + ValueEditor: ValueEditor +};
\ No newline at end of file diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js index cb166026..fa75efbe 100644 --- a/web/src/js/components/flowview/messages.js +++ b/web/src/js/components/flowview/messages.js @@ -6,6 +6,7 @@ var actions = require("../../actions.js"); var flowutils = require("../../flow/utils.js"); var utils = require("../../utils.js"); var ContentView = require("./contentview.js"); +var ValueEditor = require("../editor.js").ValueEditor; var Headers = React.createClass({ propTypes: { @@ -63,16 +64,16 @@ var Headers = React.createClass({ var rows = this.props.message.headers.map(function (header, i) { - var kEdit = <HeaderInlineInput + var kEdit = <HeaderEditor ref={i + "-key"} content={header[0]} - onChange={this.onChange.bind(null, i, 0)} + onDone={this.onChange.bind(null, i, 0)} onRemove={this.onRemove.bind(null, i, 0)} onTab={this.onTab.bind(null, i, 0)}/>; - var vEdit = <HeaderInlineInput + var vEdit = <HeaderEditor ref={i + "-value"} content={header[1]} - onChange={this.onChange.bind(null, i, 1)} + onDone={this.onChange.bind(null, i, 1)} onRemove={this.onRemove.bind(null, i, 1)} onTab={this.onTab.bind(null, i, 1)}/>; return ( @@ -92,88 +93,9 @@ var Headers = React.createClass({ } }); - -var InlineInput = React.createClass({ - mixins: [common.ChildFocus], - propTypes: { - content: React.PropTypes.string.isRequired, //must be string to match strict equality. - onChange: React.PropTypes.func.isRequired, - }, - getInitialState: function () { - return { - editable: false - }; - }, +var HeaderEditor = React.createClass({ render: function () { - var Tag = this.props.tag || "span"; - var className = "inline-input " + (this.props.className || ""); - var html = {__html: _.escape(this.props.content)}; - return <Tag - {...this.props} - tabIndex="0" - className={className} - contentEditable={this.state.editable || undefined} - onInput={this.onInput} - onFocus={this.onFocus} - onBlur={this.onBlur} - onKeyDown={this.onKeyDown} - dangerouslySetInnerHTML={html} - />; - }, - onKeyDown: function (e) { - e.stopPropagation(); - switch (e.keyCode) { - case utils.Key.ESC: - this.blur(); - break; - case utils.Key.ENTER: - e.preventDefault(); - if (!e.ctrlKey) { - this.blur(); - } else { - this.props.onDone && this.props.onDone(); - } - break; - default: - this.props.onKeyDown && this.props.onKeyDown(e); - break; - } - }, - blur: function () { - this.getDOMNode().blur(); - window.getSelection().removeAllRanges(); - this.context.returnFocus && this.context.returnFocus(); - }, - focus: function () { - React.findDOMNode(this).focus(); - var range = document.createRange(); - range.selectNodeContents(this.getDOMNode()); - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - }, - onFocus: function () { - this.setState({editable: true}, this.focus); - }, - onBlur: function (e) { - this.setState({editable: false}); - this.handleChange(); - this.props.onDone && this.props.onDone(); - }, - onInput: function () { - this.handleChange(); - }, - handleChange: function () { - var content = this.getDOMNode().textContent; - if (content !== this.props.content) { - this.props.onChange(content); - } - } -}); - -var HeaderInlineInput = React.createClass({ - render: function () { - return <InlineInput ref="input" {...this.props} onKeyDown={this.onKeyDown}/>; + return <ValueEditor ref="input" {...this.props} onKeyDown={this.onKeyDown} inline/>; }, focus: function () { this.getDOMNode().focus(); @@ -195,65 +117,6 @@ var HeaderInlineInput = React.createClass({ } }); -var ValidateInlineInput = React.createClass({ - propTypes: { - onChange: React.PropTypes.func.isRequired, - isValid: React.PropTypes.func.isRequired, - immediate: React.PropTypes.bool - }, - getInitialState: function () { - return { - content: this.props.content, - originalContent: this.props.content - }; - }, - focus: function () { - this.getDOMNode().focus(); - }, - onChange: function (val) { - this.setState({ - content: val - }); - if (this.props.immediate && val !== this.state.originalContent && this.props.isValid(val)) { - this.props.onChange(val); - } - }, - onDone: function () { - if (this.state.content === this.state.originalContent) { - return true; - } - if (this.props.isValid(this.state.content)) { - this.props.onChange(this.state.content); - } else { - this.setState({ - content: this.state.originalContent - }); - } - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.content !== this.state.content) { - this.setState({ - content: nextProps.content, - originalContent: nextProps.content - }) - } - }, - render: function () { - var className = this.props.className || ""; - if (this.props.isValid(this.state.content)) { - className += " has-success"; - } else { - className += " has-warning" - } - return <InlineInput {...this.props} - className={className} - content={this.state.content} - onChange={this.onChange} - onDone={this.onDone} - />; - } -}); - var RequestLine = React.createClass({ render: function () { var flow = this.props.flow; @@ -261,11 +124,25 @@ var RequestLine = React.createClass({ var httpver = "HTTP/" + flow.request.httpversion.join("."); return <div className="first-line request-line"> - <InlineInput ref="method" content={flow.request.method} onChange={this.onMethodChange}/> + <ValueEditor + ref="method" + content={flow.request.method} + onDone={this.onMethodChange} + inline/> - <ValidateInlineInput ref="url" content={url} onChange={this.onUrlChange} isValid={this.isValidUrl} /> + <ValueEditor + ref="url" + content={url} + onDone={this.onUrlChange} + isValid={this.isValidUrl} + inline/> - <ValidateInlineInput ref="httpVersion" immediate content={httpver} onChange={this.onHttpVersionChange} isValid={flowutils.isValidHttpVersion} /> + <ValueEditor + ref="httpVersion" + content={httpver} + onDone={this.onHttpVersionChange} + isValid={flowutils.isValidHttpVersion} + inline/> </div> }, isValidUrl: function (url) { @@ -300,12 +177,25 @@ var ResponseLine = React.createClass({ var flow = this.props.flow; var httpver = "HTTP/" + flow.response.httpversion.join("."); return <div className="first-line response-line"> - <ValidateInlineInput ref="httpVersion" immediate content={httpver} onChange={this.onHttpVersionChange} isValid={flowutils.isValidHttpVersion} /> + <ValueEditor + ref="httpVersion" + content={httpver} + onDone={this.onHttpVersionChange} + isValid={flowutils.isValidHttpVersion} + inline/> - <ValidateInlineInput ref="code" immediate content={flow.response.code + ""} onChange={this.onCodeChange} isValid={this.isValidCode} /> + <ValueEditor + ref="code" + content={flow.response.code + ""} + onDone={this.onCodeChange} + isValid={this.isValidCode} + inline/> - <InlineInput ref="msg" content={flow.response.msg} onChange={this.onMsgChange}/> - + <ValueEditor + ref="msg" + content={flow.response.msg} + onDone={this.onMsgChange} + inline/> </div>; }, isValidCode: function (code) { @@ -361,7 +251,7 @@ var Request = React.createClass({ this.refs.headers.edit(); break; default: - throw "Unimplemented: "+ k; + throw "Unimplemented: " + k; } }, onHeaderChange: function (nextHeaders) { @@ -401,7 +291,7 @@ var Response = React.createClass({ this.refs.headers.edit(); break; default: - throw "Unimplemented: "+ k; + throw "Unimplemented: " + k; } }, onHeaderChange: function (nextHeaders) { diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index 225f5b9f..998a41df 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -119,7 +119,7 @@ var FilterInput = React.createClass({ }, blur: function () { this.refs.input.getDOMNode().blur(); - this.context.returnFocus && this.context.returnFocus(); + this.returnFocus(); }, select: function () { this.refs.input.getDOMNode().select(); diff --git a/web/src/js/components/prompt.js b/web/src/js/components/prompt.js index 229e82c8..121a1170 100644 --- a/web/src/js/components/prompt.js +++ b/web/src/js/components/prompt.js @@ -34,7 +34,7 @@ var Prompt = React.createClass({ }, done: function (ret) { this.props.done(ret); - this.context.returnFocus && this.context.returnFocus(); + this.returnFocus(); }, getOptions: function () { var opts = []; |