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/js/components | |
| parent | 90dff4a8a15580cf3e86d29c6aba1f97410a0b89 (diff) | |
| download | mitmproxy-3f5ca10c39a9f7d55e0f6943caf8f6ff762a0222.tar.gz mitmproxy-3f5ca10c39a9f7d55e0f6943caf8f6ff762a0222.tar.bz2 mitmproxy-3f5ca10c39a9f7d55e0f6943caf8f6ff762a0222.zip  | |
mitmweb: add editor
Diffstat (limited to 'web/src/js/components')
| -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 = [];  | 
