aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy/web/src/js/components/editor.js
diff options
context:
space:
mode:
Diffstat (limited to 'mitmproxy/web/src/js/components/editor.js')
-rw-r--r--mitmproxy/web/src/js/components/editor.js240
1 files changed, 240 insertions, 0 deletions
diff --git a/mitmproxy/web/src/js/components/editor.js b/mitmproxy/web/src/js/components/editor.js
new file mode 100644
index 00000000..f2d44566
--- /dev/null
+++ b/mitmproxy/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