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 ) 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 ; }, 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 ; }, 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 ; }, focus: function () { React.findDOMNode(this).focus(); }, onStop: function () { this.returnFocus(); } }); module.exports = { ValueEditor: ValueEditor };