diff options
author | Jim Shaver <dcypherd@gmail.com> | 2015-06-23 21:48:05 -0500 |
---|---|---|
committer | Jim Shaver <dcypherd@gmail.com> | 2015-06-23 21:48:05 -0500 |
commit | 080e4534253338c94e6d8c86cb3679ff15410f85 (patch) | |
tree | 6322fb822332b4135f0ff14de8c2d7137016f734 /web/src/js/components/editor.js | |
parent | db5c0b210b0133d7cd58124c727dbc24480e2568 (diff) | |
parent | 074d8d7c7463cdb1f0a90e165a4b3ada3554b4c2 (diff) | |
download | mitmproxy-080e4534253338c94e6d8c86cb3679ff15410f85.tar.gz mitmproxy-080e4534253338c94e6d8c86cb3679ff15410f85.tar.bz2 mitmproxy-080e4534253338c94e6d8c86cb3679ff15410f85.zip |
Merge branch 'master' into hardfailvenv
Conflicts:
dev
Diffstat (limited to 'web/src/js/components/editor.js')
-rw-r--r-- | web/src/js/components/editor.js | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/web/src/js/components/editor.js b/web/src/js/components/editor.js new file mode 100644 index 00000000..f2d44566 --- /dev/null +++ b/web/src/js/components/editor.js @@ -0,0 +1,240 @@ +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)> ) + onStop: React.PropTypes.func, + 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} + onMouseDown={this.onMouseDown} + onClick={this.onClick} + onBlur={this._stop} + onKeyDown={this.onKeyDown} + onInput={this.onInput} + onPaste={this.onPaste} + dangerouslySetInnerHTML={html} + />; + }, + onPaste: function (e) { + e.preventDefault(); + var content = e.clipboardData.getData("text/plain"); + document.execCommand("insertHTML", false, content); + }, + onMouseDown: function (e) { + this._mouseDown = true; + window.addEventListener("mouseup", this.onMouseUp); + this.props.onMouseDown && this.props.onMouseDown(e); + }, + onMouseUp: function () { + if (this._mouseDown) { + this._mouseDown = false; + window.removeEventListener("mouseup", this.onMouseUp) + } + }, + onClick: function (e) { + this.onMouseUp(); + this.onFocus(e); + }, + onFocus: function (e) { + console.log("onFocus", this._mouseDown, this._ignore_events, this.state.editable); + if (this._mouseDown || this._ignore_events || this.state.editable) { + return; + } + + //contenteditable in FireFox is more or less broken. + // - we need to blur() and then focus(), otherwise the caret is not shown. + // - blur() + focus() == we need to save the caret position before + // Firefox sometimes just doesn't set a caret position => use caretPositionFromPoint + var sel = window.getSelection(); + var range; + if (sel.rangeCount > 0) { + range = sel.getRangeAt(0); + } else if (document.caretPositionFromPoint && e.clientX && e.clientY) { + var pos = document.caretPositionFromPoint(e.clientX, e.clientY); + range = document.createRange(); + range.setStart(pos.offsetNode, pos.offset); + } else if (document.caretRangeFromPoint && e.clientX && e.clientY) { + range = document.caretRangeFromPoint(e.clientX, e.clientY); + } else { + range = document.createRange(); + range.selectNodeContents(React.findDOMNode(this)); + } + + this._ignore_events = true; + this.setState({editable: true}, function () { + var node = React.findDOMNode(this); + node.blur(); + node.focus(); + this._ignore_events = false; + //sel.removeAllRanges(); + //sel.addRange(range); + + + }); + }, + 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(); + this.props.onStop && this.props.onStop(); + }, + _stop: function (e) { + if (this._ignore_events) { + return; + } + console.log("_stop", _.extend({}, 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); + }, + reset: function () { + React.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content); + }, + onKeyDown: function (e) { + e.stopPropagation(); + switch (e.keyCode) { + case utils.Key.ESC: + e.preventDefault(); + this.reset(); + this.stop(); + break; + case utils.Key.ENTER: + if (this.props.submitOnEnter && !e.shiftKey) { + e.preventDefault(); + this.stop(); + } + break; + default: + break; + } + }, + onInput: function () { + var node = React.findDOMNode(this); + var content = this.props.nodeToContent(node); + 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.reset(); + 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} + onStop={this.onStop} + tag={tag} + />; + }, + focus: function () { + React.findDOMNode(this).focus(); + }, + onStop: function () { + this.returnFocus(); + } +}); + +module.exports = { + ValueEditor: ValueEditor +};
\ No newline at end of file |