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 ( ) } 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))) } }