Julian Steinwachs
9 years ago
8 changed files with 941 additions and 1343 deletions
@ -1,345 +0,0 @@ |
|||||||
var React = require('react') |
|
||||||
var ReactDOM = require('react-dom') |
|
||||||
var classNames = require('classnames') |
|
||||||
var escapeHTML = require('escape-html') |
|
||||||
var isServer = typeof window === 'undefined' |
|
||||||
|
|
||||||
if (!isServer) { |
|
||||||
var selectionRange = require('selection-range') |
|
||||||
} |
|
||||||
|
|
||||||
var noop = function(){} |
|
||||||
|
|
||||||
/** |
|
||||||
* Make a contenteditable element |
|
||||||
*/ |
|
||||||
|
|
||||||
var ContentEditable = React.createClass({ |
|
||||||
|
|
||||||
propTypes: { |
|
||||||
editing: React.PropTypes.bool, |
|
||||||
html: React.PropTypes.string, |
|
||||||
onChange: React.PropTypes.func.isRequired, |
|
||||||
placeholder: React.PropTypes.bool, |
|
||||||
placeholderText: React.PropTypes.string, |
|
||||||
tagName: React.PropTypes.string, |
|
||||||
onEnterKey: React.PropTypes.func, |
|
||||||
onEscapeKey: React.PropTypes.func, |
|
||||||
preventStyling: React.PropTypes.bool, |
|
||||||
noLinebreaks: React.PropTypes.bool, |
|
||||||
onBlur: React.PropTypes.func, |
|
||||||
onFocus: React.PropTypes.func, |
|
||||||
onBold: React.PropTypes.func, |
|
||||||
onItalic: React.PropTypes.func, |
|
||||||
onKeyDown: React.PropTypes.func, |
|
||||||
onKeyPress: React.PropTypes.func, |
|
||||||
placeholderStyle: React.PropTypes.object |
|
||||||
}, |
|
||||||
|
|
||||||
getDefaultProps: function() { |
|
||||||
return { |
|
||||||
html: '', |
|
||||||
placeholder: false, |
|
||||||
placeholderText: '', |
|
||||||
onBold: noop, |
|
||||||
onItalic: noop, |
|
||||||
onKeyDown: noop, |
|
||||||
onKeyPress: noop |
|
||||||
}; |
|
||||||
}, |
|
||||||
|
|
||||||
getInitialState: function(){ |
|
||||||
return {}; |
|
||||||
}, |
|
||||||
|
|
||||||
shouldComponentUpdate: function(nextProps) { |
|
||||||
var el = ReactDOM.findDOMNode(this) |
|
||||||
if (nextProps.html !== el.innerHTML) { |
|
||||||
if (nextProps.html && document.activeElement === el) { |
|
||||||
this._range = selectionRange(el) |
|
||||||
} |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
if (nextProps.placeholder !== this.props.placeholder) { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
if (nextProps.editing !== this.props.editing) { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
return false |
|
||||||
}, |
|
||||||
|
|
||||||
componentWillReceiveProps: function (nextProps) { |
|
||||||
if (!this.props.editing && nextProps.editing) { |
|
||||||
if (this.contentIsEmpty(nextProps.html)) { |
|
||||||
this.props.onChange('', true) |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
componentDidUpdate: function() { |
|
||||||
if (!this.props.editing && !this.props.html) { |
|
||||||
this.props.onChange('') |
|
||||||
} |
|
||||||
|
|
||||||
if (this._range) { |
|
||||||
selectionRange(ReactDOM.findDOMNode(this), this._range) |
|
||||||
delete this._range |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
autofocus: function(){ |
|
||||||
ReactDOM.findDOMNode(this).focus(); |
|
||||||
}, |
|
||||||
|
|
||||||
render: function() { |
|
||||||
|
|
||||||
// todo: use destructuring
|
|
||||||
var editing = this.props.editing; |
|
||||||
var className = this.props.className; |
|
||||||
var tagName = this.props.tagName; |
|
||||||
|
|
||||||
// setup our classes
|
|
||||||
var classes = { |
|
||||||
ContentEditable: true |
|
||||||
}; |
|
||||||
|
|
||||||
var placeholderStyle = this.props.placeholderStyle || { |
|
||||||
color: '#bbbbbb' |
|
||||||
} |
|
||||||
|
|
||||||
if (className) { |
|
||||||
classes[className] = true; |
|
||||||
} |
|
||||||
|
|
||||||
// set 'div' as our default tagname
|
|
||||||
tagName = tagName || 'div'; |
|
||||||
|
|
||||||
var content = this.props.html; |
|
||||||
|
|
||||||
// return our newly created element
|
|
||||||
return React.createElement(tagName, { |
|
||||||
tabIndex: 0, |
|
||||||
key: '0', |
|
||||||
className: classNames(classes), |
|
||||||
contentEditable: editing, |
|
||||||
onBlur: this.onBlur, |
|
||||||
onFocus: this.onFocus, |
|
||||||
onKeyDown: this.onKeyDown, |
|
||||||
onPaste: this.onPaste, |
|
||||||
onMouseDown: this.onMouseDown, |
|
||||||
'aria-label': this.props.placeholderText, |
|
||||||
onTouchStart: this.onMouseDown, |
|
||||||
style: this.props.placeholder ? placeholderStyle : this.props.style || {}, |
|
||||||
onKeyPress: this.onKeyPress, |
|
||||||
onInput: this.onInput, |
|
||||||
onKeyUp: this.onKeyUp, |
|
||||||
dangerouslySetInnerHTML: { |
|
||||||
__html : this.props.placeholder ? this.props.placeholderText : content |
|
||||||
} |
|
||||||
}); |
|
||||||
}, |
|
||||||
|
|
||||||
unsetPlaceholder: function(){ |
|
||||||
this.props.onChange('', false, '') |
|
||||||
}, |
|
||||||
|
|
||||||
setCursorToStart: function(){ |
|
||||||
ReactDOM.findDOMNode(this).focus(); |
|
||||||
var sel = window.getSelection(); |
|
||||||
var range = document.createRange(); |
|
||||||
range.setStart(ReactDOM.findDOMNode(this), 0); |
|
||||||
range.collapse(true); |
|
||||||
sel.removeAllRanges(); |
|
||||||
sel.addRange(range); |
|
||||||
}, |
|
||||||
|
|
||||||
contentIsEmpty: function (content) { |
|
||||||
|
|
||||||
if (this.state.placeholder) { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
if (!content) { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
if (content === '<br />') { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
if (!content.trim().length) { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
return false |
|
||||||
}, |
|
||||||
|
|
||||||
|
|
||||||
onMouseDown: function(e) { |
|
||||||
// prevent cursor placement if placeholder is set
|
|
||||||
if (this.contentIsEmpty(this.props.html)) { |
|
||||||
this.setCursorToStart() |
|
||||||
e.preventDefault() |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
onKeyDown: function(e) { |
|
||||||
var self = this |
|
||||||
this.props.onKeyDown(e) |
|
||||||
|
|
||||||
function prev () { |
|
||||||
e.preventDefault(); |
|
||||||
e.stopPropagation(); |
|
||||||
self.stop = true |
|
||||||
} |
|
||||||
|
|
||||||
var key = e.keyCode; |
|
||||||
|
|
||||||
// bold & italic styling
|
|
||||||
if (e.metaKey) { |
|
||||||
|
|
||||||
// bold
|
|
||||||
if (key === 66) { |
|
||||||
this.props.onBold(e) |
|
||||||
if (this.props.preventStyling) { |
|
||||||
return prev() |
|
||||||
} |
|
||||||
|
|
||||||
// italic
|
|
||||||
} else if (key === 73) { |
|
||||||
this.props.onItalic(e) |
|
||||||
if (this.props.preventStyling) { |
|
||||||
return prev() |
|
||||||
} |
|
||||||
//paste
|
|
||||||
} else if (key === 86) { |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// prevent linebreaks
|
|
||||||
if (this.props.noLinebreaks && (key === 13)) { |
|
||||||
return prev() |
|
||||||
} |
|
||||||
|
|
||||||
// placeholder behaviour
|
|
||||||
if (this.contentIsEmpty(this.props.html)) { // If no text
|
|
||||||
|
|
||||||
if (e.metaKey || (e.shiftKey && (key === 16))) { |
|
||||||
return prev() |
|
||||||
} |
|
||||||
|
|
||||||
switch (key) { |
|
||||||
case 46: // 'Delete' key
|
|
||||||
case 8: // 'Backspace' key
|
|
||||||
case 9: // 'Tab' key
|
|
||||||
case 39: // 'Arrow right' key
|
|
||||||
case 37: // 'Arrow left' key
|
|
||||||
case 40: // 'Arrow left' key
|
|
||||||
case 38: // 'Arrow left' key
|
|
||||||
prev(); |
|
||||||
break; |
|
||||||
|
|
||||||
case 13: |
|
||||||
// 'Enter' key
|
|
||||||
prev(); |
|
||||||
if (this.props.onEnterKey) { |
|
||||||
this.props.onEnterKey(); |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
case 27: |
|
||||||
// 'Escape' key
|
|
||||||
prev(); |
|
||||||
if (this.props.onEscapeKey) { |
|
||||||
this.props.onEscapeKey(); |
|
||||||
} |
|
||||||
break; |
|
||||||
|
|
||||||
default: |
|
||||||
this.unsetPlaceholder(); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
_replaceCurrentSelection: function(data) { |
|
||||||
var selection = window.getSelection(); |
|
||||||
var range = selection.getRangeAt(0); |
|
||||||
range.deleteContents(); |
|
||||||
var fragment = range.createContextualFragment(''); |
|
||||||
fragment.textContent = data; |
|
||||||
var replacementEnd = fragment.lastChild; |
|
||||||
range.insertNode(fragment); |
|
||||||
// Set cursor at the end of the replaced content, just like browsers do.
|
|
||||||
range.setStartAfter(replacementEnd); |
|
||||||
range.collapse(true); |
|
||||||
selection.removeAllRanges(); |
|
||||||
selection.addRange(range); |
|
||||||
}, |
|
||||||
|
|
||||||
onPaste: function(e){ |
|
||||||
// handle paste manually to ensure we unset our placeholder
|
|
||||||
e.preventDefault(); |
|
||||||
var data = e.clipboardData.getData('text/plain') |
|
||||||
this._replaceCurrentSelection(data); |
|
||||||
var target = ReactDOM.findDOMNode(this) |
|
||||||
this.props.onChange(target.textContent, false, target.innerHTML) |
|
||||||
}, |
|
||||||
|
|
||||||
onKeyPress: function(e){ |
|
||||||
this.props.onKeyPress(e) |
|
||||||
}, |
|
||||||
|
|
||||||
onKeyUp: function(e) { |
|
||||||
if (this._supportsInput) return |
|
||||||
if (this.stop) { |
|
||||||
this.stop = false |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
var target = ReactDOM.findDOMNode(this) |
|
||||||
var self = this |
|
||||||
|
|
||||||
if (!target.textContent.trim().length) { |
|
||||||
this.props.onChange('', true, '') |
|
||||||
setTimeout(function(){ |
|
||||||
self.setCursorToStart() |
|
||||||
}, 1) |
|
||||||
} else { |
|
||||||
this.props.onChange(target.textContent, false, target.innerHTML) |
|
||||||
} |
|
||||||
|
|
||||||
}, |
|
||||||
|
|
||||||
onInput: function(e) { |
|
||||||
this._supportsInput = true |
|
||||||
var val = e.target.innerHTML |
|
||||||
var text = e.target.textContent.trim() |
|
||||||
if (!text) { |
|
||||||
this.props.onChange('', true, '') |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
this.props.onChange(escapeHTML(e.target.textContent), false, e.target.innerHTML) |
|
||||||
}, |
|
||||||
|
|
||||||
onBlur: function(e) { |
|
||||||
if (this.props.onBlur) { |
|
||||||
this.props.onBlur(e); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
onFocus: function(e) { |
|
||||||
if (this.props.onFocus) { |
|
||||||
this.props.onFocus(e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
}); |
|
||||||
|
|
||||||
module.exports = ContentEditable; |
|
@ -1,45 +0,0 @@ |
|||||||
var React = require('react'); |
|
||||||
var ContentEditable = require('../common/ContentEditable.js'); |
|
||||||
|
|
||||||
module.exports = TwistComposer = React.createClass({ displayName: "TwistComposer", |
|
||||||
|
|
||||||
getInitialState: function () { |
|
||||||
return { |
|
||||||
html: 'default text', |
|
||||||
placeholder: true, |
|
||||||
editing: true |
|
||||||
}; |
|
||||||
}, |
|
||||||
|
|
||||||
render: function () { |
|
||||||
return React.createElement("div", null, React.createElement(ContentEditable, { |
|
||||||
tagName: "textarea", |
|
||||||
onChange: this.onChange, |
|
||||||
html: this.state.html, |
|
||||||
preventStyling: true, |
|
||||||
noLinebreaks: true, |
|
||||||
placeholder: this.state.placeholder, |
|
||||||
placeholderText: "Your Name", |
|
||||||
editing: this.state.editing }), React.createElement("button", { onClick: this.enableEditing }, "Enable Editing")); |
|
||||||
}, |
|
||||||
|
|
||||||
onChange: function (textContent, setPlaceholder) { |
|
||||||
if (setPlaceholder) { |
|
||||||
this.setState({ |
|
||||||
placeholder: true, |
|
||||||
html: '' |
|
||||||
}); |
|
||||||
} else { |
|
||||||
this.setState({ |
|
||||||
placeholder: false, |
|
||||||
html: textContent |
|
||||||
}); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
enableEditing: function () { |
|
||||||
// set your contenteditable field into editing mode.
|
|
||||||
this.setState({ editing: true }); |
|
||||||
} |
|
||||||
|
|
||||||
}); |
|
@ -1,5 +1,4 @@ |
|||||||
var PostContent = require("../build-buffer/common/PostContent.js"); |
var PostContentHelper = require("../build-buffer/common/PostContentHelper.js"); |
||||||
|
|
||||||
var sut = new PostContent(); |
|
||||||
|
|
||||||
console.log(sut.parseContent(".@tschaul so cool, great work!")); |
console.log(PostContentHelper.parseContent("asd http://")); |
Loading…
Reference in new issue