aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js/components/ValueEditor
diff options
context:
space:
mode:
authorJason <jason.daurus@gmail.com>2016-06-15 00:46:59 +0800
committerJason <jason.daurus@gmail.com>2016-06-17 04:38:35 +0800
commitf5c597a9e351b8dfb84f0fe3f09046e772482fc6 (patch)
tree8f44cc55513a04b64b4e79d4d9c18f59f100cfaf /web/src/js/components/ValueEditor
parente5bf1e930a5b6ba0b3300b02daf792d65d795202 (diff)
downloadmitmproxy-f5c597a9e351b8dfb84f0fe3f09046e772482fc6.tar.gz
mitmproxy-f5c597a9e351b8dfb84f0fe3f09046e772482fc6.tar.bz2
mitmproxy-f5c597a9e351b8dfb84f0fe3f09046e772482fc6.zip
[web] Editor & Prompt
Diffstat (limited to 'web/src/js/components/ValueEditor')
-rwxr-xr-xweb/src/js/components/ValueEditor/EditorBase.jsx166
-rwxr-xr-xweb/src/js/components/ValueEditor/ValidateEditor.jsx58
2 files changed, 224 insertions, 0 deletions
diff --git a/web/src/js/components/ValueEditor/EditorBase.jsx b/web/src/js/components/ValueEditor/EditorBase.jsx
new file mode 100755
index 00000000..b659cf6d
--- /dev/null
+++ b/web/src/js/components/ValueEditor/EditorBase.jsx
@@ -0,0 +1,166 @@
+import React, { Component, PropTypes } from 'react'
+import ReactDOM from 'react-dom'
+import {Key} from '../../utils.js'
+
+export default class EditorBase extends Component {
+
+ static propTypes = {
+ content: PropTypes.string.isRequired,
+ onDone: PropTypes.func.isRequired,
+ contentToHtml: PropTypes.func,
+ nodeToContent: PropTypes.func,
+ onStop: PropTypes.func,
+ submitOnEnter: PropTypes.bool,
+ className: PropTypes.string,
+ tag: PropTypes.string,
+ }
+
+ static defaultProps = {
+ contentToHtml: content => _.escape(content),
+ nodeToContent: node => node.textContent,
+ submitOnEnter: true,
+ className: '',
+ tag: 'div',
+ onStop: _.noop,
+ onMouseDown: _.noop,
+ onBlur: _.noop,
+ onInput: _.noop,
+ }
+
+ constructor(props) {
+ super(props)
+ this.state = {editable: false}
+
+ this.onPaste = this.onPaste.bind(this)
+ this.onMouseDown = this.onMouseDown.bind(this)
+ this.onMouseUp = this.onMouseUp.bind(this)
+ this.onFocus = this.onFocus.bind(this)
+ this.onClick = this.onClick.bind(this)
+ this.stop = this.stop.bind(this)
+ this.onBlur = this.onBlur.bind(this)
+ this.reset = this.reset.bind(this)
+ this.onKeyDown = this.onKeyDown.bind(this)
+ this.onInput = this.onInput.bind(this)
+ }
+
+ stop() {
+ // 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.
+ ReactDOM.findDOMNode(this).blur()
+ this.props.onStop()
+ }
+
+ render() {
+ return (
+ <this.props.tag
+ {...this.props}
+ tabIndex="0"
+ className={`inline-input ${this.props.className}`}
+ contentEditable={this.state.editable || undefined}
+ onFocus={this.onFocus}
+ onMouseDown={this.onMouseDown}
+ onClick={this.onClick}
+ onBlur={this.onBlur}
+ onKeyDown={this.onKeyDown}
+ onInput={this.onInput}
+ onPaste={this.onPaste}
+ dangerouslySetInnerHTML={{ __html: this.props.contentToHtml(this.props.content) }}
+ />
+ )
+ }
+
+ onPaste(e) {
+ e.preventDefault()
+ var content = e.clipboardData.getData('text/plain')
+ document.execCommand('insertHTML', false, content)
+ }
+
+ onMouseDown(e) {
+ this._mouseDown = true
+ window.addEventListener('mouseup', this.onMouseUp)
+ this.props.onMouseDown(e)
+ }
+
+ onMouseUp() {
+ if (this._mouseDown) {
+ this._mouseDown = false
+ window.removeEventListener('mouseup', this.onMouseUp)
+ }
+ }
+
+ onClick(e) {
+ this.onMouseUp()
+ this.onFocus(e)
+ }
+
+ onFocus(e) {
+ 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
+ const sel = window.getSelection()
+ let range
+ if (sel.rangeCount > 0) {
+ range = sel.getRangeAt(0)
+ } else if (document.caretPositionFromPoint && e.clientX && e.clientY) {
+ const 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(ReactDOM.findDOMNode(this))
+ }
+
+ this._ignore_events = true
+ this.setState({ editable: true }, () => {
+ const node = ReactDOM.findDOMNode(this)
+ node.blur()
+ node.focus()
+ this._ignore_events = false
+ })
+ }
+
+ onBlur(e) {
+ if (this._ignore_events) {
+ return
+ }
+ window.getSelection().removeAllRanges() //make sure that selection is cleared on blur
+ this.setState({ editable: false })
+ this.props.onDone(this.props.nodeToContent(ReactDOM.findDOMNode(this)))
+ this.props.onBlur(e)
+ }
+
+ reset() {
+ ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content)
+ }
+
+ onKeyDown(e) {
+ e.stopPropagation()
+ switch (e.keyCode) {
+ case Key.ESC:
+ e.preventDefault()
+ this.reset()
+ this.stop()
+ break
+ case Key.ENTER:
+ if (this.props.submitOnEnter && !e.shiftKey) {
+ e.preventDefault()
+ this.stop()
+ }
+ break
+ default:
+ break
+ }
+ }
+
+ onInput() {
+ this.props.onInput(this.props.nodeToContent(ReactDOM.findDOMNode(this)))
+ }
+}
diff --git a/web/src/js/components/ValueEditor/ValidateEditor.jsx b/web/src/js/components/ValueEditor/ValidateEditor.jsx
new file mode 100755
index 00000000..ea4e8803
--- /dev/null
+++ b/web/src/js/components/ValueEditor/ValidateEditor.jsx
@@ -0,0 +1,58 @@
+import React, { Component, PropTypes } from 'react'
+import ReactDOM from 'react-dom'
+import EditorBase from './EditorBase'
+
+export default class ValidateEditor extends Component {
+
+ static propTypes = {
+ content: PropTypes.string.isRequired,
+ onDone: PropTypes.func.isRequired,
+ onInput: PropTypes.func,
+ isValid: PropTypes.func,
+ className: PropTypes.string,
+ }
+
+ constructor(props) {
+ super(props)
+ this.state = { currentContent: props.content }
+ this.onInput = this.onInput.bind(this)
+ this.onDone = this.onDone.bind(this)
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({ currentContent: nextProps.content })
+ }
+
+ onInput(currentContent) {
+ this.setState({ currentContent })
+ this.props.onInput && this.props.onInput(currentContent)
+ }
+
+ onDone(content) {
+ if (this.props.isValid && !this.props.isValid(content)) {
+ this.refs.editor.reset()
+ content = this.props.content
+ }
+ this.props.onDone(content)
+ }
+
+ render() {
+ let 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}
+ />
+ )
+ }
+}