mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 02:10:59 +00:00
[web] Editor & Prompt
This commit is contained in:
parent
e5bf1e930a
commit
f5c597a9e3
File diff suppressed because one or more lines are too long
@ -4,7 +4,7 @@ import _ from 'lodash'
|
|||||||
import Nav from './FlowView/Nav'
|
import Nav from './FlowView/Nav'
|
||||||
import { Request, Response, Error } from './FlowView/Messages'
|
import { Request, Response, Error } from './FlowView/Messages'
|
||||||
import Details from './FlowView/Details'
|
import Details from './FlowView/Details'
|
||||||
import Prompt from './prompt'
|
import Prompt from './Prompt'
|
||||||
|
|
||||||
export default class FlowView extends Component {
|
export default class FlowView extends Component {
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ export default class FlowView extends Component {
|
|||||||
active={active}
|
active={active}
|
||||||
onSelectTab={this.selectTab}
|
onSelectTab={this.selectTab}
|
||||||
/>
|
/>
|
||||||
<Tab flow={flow}/>
|
<Tab ref="tab" flow={flow}/>
|
||||||
{this.state.prompt && (
|
{this.state.prompt && (
|
||||||
<Prompt {...this.state.prompt}/>
|
<Prompt {...this.state.prompt}/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import { ValueEditor } from '../editor'
|
import ValueEditor from '../ValueEditor'
|
||||||
import { Key } from '../../utils.js'
|
import { Key } from '../../utils.js'
|
||||||
|
|
||||||
class HeaderEditor extends Component {
|
class HeaderEditor extends Component {
|
||||||
|
@ -5,7 +5,7 @@ import { FlowActions } from '../../actions.js'
|
|||||||
import { RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion } from '../../flow/utils.js'
|
import { RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion } from '../../flow/utils.js'
|
||||||
import { Key, formatTimeStamp } from '../../utils.js'
|
import { Key, formatTimeStamp } from '../../utils.js'
|
||||||
import ContentView from '../ContentView'
|
import ContentView from '../ContentView'
|
||||||
import { ValueEditor } from '../editor'
|
import ValueEditor from '../ValueEditor'
|
||||||
import Headers from './Headers'
|
import Headers from './Headers'
|
||||||
|
|
||||||
class RequestLine extends Component {
|
class RequestLine extends Component {
|
||||||
|
75
web/src/js/components/Prompt.jsx
Executable file
75
web/src/js/components/Prompt.jsx
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import {Key} from '../utils.js'
|
||||||
|
|
||||||
|
Prompt.contextTypes = {
|
||||||
|
returnFocus: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
Prompt.propTypes = {
|
||||||
|
options: PropTypes.array.isRequired,
|
||||||
|
done: PropTypes.func.isRequired,
|
||||||
|
prompt: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
Prompt.componentDidMount = function() {
|
||||||
|
ReactDOM.findDOMNode(this).focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Prompt({ prompt, done, options }, context) {
|
||||||
|
const opts = []
|
||||||
|
|
||||||
|
function keyTaken(k) {
|
||||||
|
return _.map(opts, 'key').includes(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < options.length; i++) {
|
||||||
|
let opt = options[i]
|
||||||
|
if (_.isString(opt)) {
|
||||||
|
let str = opt
|
||||||
|
while (str.length > 0 && keyTaken(str[0])) {
|
||||||
|
str = str.substr(1)
|
||||||
|
}
|
||||||
|
opt = { text: opt, key: str[0] }
|
||||||
|
}
|
||||||
|
if (!opt.text || !opt.key || keyTaken(opt.key)) {
|
||||||
|
throw 'invalid options'
|
||||||
|
}
|
||||||
|
opts.push(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div tabIndex="0" onKeyDown={onKeyDown} onClick={onClick} className="prompt-dialog">
|
||||||
|
<div className="prompt-content">
|
||||||
|
{prompt || <strong>Select: </strong> }
|
||||||
|
{opts.map(opt => {
|
||||||
|
const idx = opt.text.indexOf(opt.key)
|
||||||
|
function onClick(event) {
|
||||||
|
done(opt.key)
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span key={opt.key} className="option" onClick={onClick}>
|
||||||
|
{idx !== -1 ? opt.text.substring(0, idx) : opt.text + '('}
|
||||||
|
{prefix}<strong className="text-primary">{opt.key}</strong>
|
||||||
|
{idx !== -1 ? opt.text.substring(idx + 1) : ')'}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
function onKeyDown(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
event.preventDefault()
|
||||||
|
const key = opts.find(opt => Key[opt.key.toUpperCase()] === event.keyCode)
|
||||||
|
if (!key && event.keyCode !== Key.ESC && event.keyCode !== Key.ENTER) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done(k || false)
|
||||||
|
context.returnFocus()
|
||||||
|
}
|
||||||
|
}
|
36
web/src/js/components/ValueEditor.jsx
Executable file
36
web/src/js/components/ValueEditor.jsx
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
import React, { Component, PropTypes } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import ValidateEditor from './ValueEditor/ValidateEditor'
|
||||||
|
|
||||||
|
export default class ValueEditor extends Component {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
returnFocus: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
onDone: PropTypes.func.isRequired,
|
||||||
|
inline: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.focus = this.focus.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var tag = this.props.inline ? "span" : 'div'
|
||||||
|
return (
|
||||||
|
<ValidateEditor
|
||||||
|
{...this.props}
|
||||||
|
onStop={() => this.context.returnFocus()}
|
||||||
|
tag={tag}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
ReactDOM.findDOMNode(this).focus();
|
||||||
|
}
|
||||||
|
}
|
166
web/src/js/components/ValueEditor/EditorBase.jsx
Executable file
166
web/src/js/components/ValueEditor/EditorBase.jsx
Executable file
@ -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)))
|
||||||
|
}
|
||||||
|
}
|
58
web/src/js/components/ValueEditor/ValidateEditor.jsx
Executable file
58
web/src/js/components/ValueEditor/ValidateEditor.jsx
Executable file
@ -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}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,238 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import {Key} from "../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(ReactDOM.findDOMNode(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
this._ignore_events = true;
|
|
||||||
this.setState({editable: true}, function () {
|
|
||||||
var node = ReactDOM.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.
|
|
||||||
ReactDOM.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 = ReactDOM.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 () {
|
|
||||||
ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content);
|
|
||||||
},
|
|
||||||
onKeyDown: function (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: function () {
|
|
||||||
var node = ReactDOM.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
|
|
||||||
*/
|
|
||||||
export var ValueEditor = React.createClass({
|
|
||||||
contextTypes: {
|
|
||||||
returnFocus: React.PropTypes.func
|
|
||||||
},
|
|
||||||
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 () {
|
|
||||||
ReactDOM.findDOMNode(this).focus();
|
|
||||||
},
|
|
||||||
onStop: function () {
|
|
||||||
this.context.returnFocus();
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,102 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
import {Key} from "../utils.js";
|
|
||||||
|
|
||||||
var Prompt = React.createClass({
|
|
||||||
contextTypes: {
|
|
||||||
returnFocus: React.PropTypes.func
|
|
||||||
},
|
|
||||||
propTypes: {
|
|
||||||
options: React.PropTypes.array.isRequired,
|
|
||||||
done: React.PropTypes.func.isRequired,
|
|
||||||
prompt: React.PropTypes.string
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
ReactDOM.findDOMNode(this).focus();
|
|
||||||
},
|
|
||||||
onKeyDown: function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
var opts = this.getOptions();
|
|
||||||
for (var i = 0; i < opts.length; i++) {
|
|
||||||
var k = opts[i].key;
|
|
||||||
if (Key[k.toUpperCase()] === e.keyCode) {
|
|
||||||
this.done(k);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) {
|
|
||||||
this.done(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick: function (e) {
|
|
||||||
this.done(false);
|
|
||||||
},
|
|
||||||
done: function (ret) {
|
|
||||||
this.props.done(ret);
|
|
||||||
this.context.returnFocus();
|
|
||||||
},
|
|
||||||
getOptions: function () {
|
|
||||||
var opts = [];
|
|
||||||
|
|
||||||
var keyTaken = function (k) {
|
|
||||||
return _.includes(_.map(opts, "key"), k);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var i = 0; i < this.props.options.length; i++) {
|
|
||||||
var opt = this.props.options[i];
|
|
||||||
if (_.isString(opt)) {
|
|
||||||
var str = opt;
|
|
||||||
while (str.length > 0 && keyTaken(str[0])) {
|
|
||||||
str = str.substr(1);
|
|
||||||
}
|
|
||||||
opt = {
|
|
||||||
text: opt,
|
|
||||||
key: str[0]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!opt.text || !opt.key || keyTaken(opt.key)) {
|
|
||||||
throw "invalid options";
|
|
||||||
} else {
|
|
||||||
opts.push(opt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return opts;
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var opts = this.getOptions();
|
|
||||||
opts = _.map(opts, function (o) {
|
|
||||||
var prefix, suffix;
|
|
||||||
var idx = o.text.indexOf(o.key);
|
|
||||||
if (idx !== -1) {
|
|
||||||
prefix = o.text.substring(0, idx);
|
|
||||||
suffix = o.text.substring(idx + 1);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
prefix = o.text + " (";
|
|
||||||
suffix = ")";
|
|
||||||
}
|
|
||||||
var onClick = function (e) {
|
|
||||||
this.done(o.key);
|
|
||||||
e.stopPropagation();
|
|
||||||
}.bind(this);
|
|
||||||
return <span
|
|
||||||
key={o.key}
|
|
||||||
className="option"
|
|
||||||
onClick={onClick}>
|
|
||||||
{prefix}
|
|
||||||
<strong className="text-primary">{o.key}</strong>{suffix}
|
|
||||||
</span>;
|
|
||||||
}.bind(this));
|
|
||||||
return <div tabIndex="0" onKeyDown={this.onKeyDown} onClick={this.onClick} className="prompt-dialog">
|
|
||||||
<div className="prompt-content">
|
|
||||||
{this.props.prompt || <strong>Select: </strong> }
|
|
||||||
{opts}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Prompt;
|
|
Loading…
Reference in New Issue
Block a user