mirror of
https://github.com/twisterarmy/twister-react.git
synced 2025-01-27 15:14:30 +00:00
wysiwyg somewhat
This commit is contained in:
parent
e51c9ce13e
commit
e26d5fbc0b
1698
build/app-bundle.js
1698
build/app-bundle.js
File diff suppressed because it is too large
Load Diff
@ -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;
|
@ -6,6 +6,7 @@ var ReactBootstrap = require('react-bootstrap')
|
||||
, Glyphicon = ReactBootstrap.Glyphicon
|
||||
, Modal = ReactBootstrap.Modal
|
||||
, Input = ReactBootstrap.Input
|
||||
, Button = ReactBootstrap.Button
|
||||
|
||||
var React = require('react');
|
||||
|
||||
@ -74,7 +75,7 @@ module.exports = FollowButton = React.createClass({
|
||||
var methodName = this.state.isCurrentlyFollowing ? "Unfollow" : "Follow";
|
||||
|
||||
return (
|
||||
<button onClick={this.handleClick}>{methodName}</button>
|
||||
<Button onClick={this.handleClick}>{methodName}</Button>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,155 +1,12 @@
|
||||
|
||||
var React = require('react');
|
||||
var PostContentHelper = require('../common/PostContentHelper.js');
|
||||
|
||||
module.exports = Post = React.createClass({
|
||||
|
||||
extractUsername: function(s) {
|
||||
var username = "";
|
||||
for( var i = 0; i < s.length; i++ ) {
|
||||
var c = s.charCodeAt(i);
|
||||
if( (c >= 'a'.charCodeAt(0) && c <= 'z'.charCodeAt(0)) ||
|
||||
(c >= 'A'.charCodeAt(0) && c <= 'Z'.charCodeAt(0)) ||
|
||||
(c >= '0'.charCodeAt(0) && c <= '9'.charCodeAt(0)) ||
|
||||
c == '_'.charCodeAt(0) ) {
|
||||
username += s[i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return username;
|
||||
},
|
||||
extractHashtag: function(s) {
|
||||
var hashtag = "";
|
||||
s = this.reverseHtmlEntities(s);
|
||||
for( var i = 0; i < s.length; i++ ) {
|
||||
if( " \n\t.,:/?!;'\"()[]{}*#".indexOf(s[i]) < 0 ) {
|
||||
hashtag += s[i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hashtag;
|
||||
},
|
||||
escapeHtmlEntities: function(str) {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
},
|
||||
reverseHtmlEntities: function(str) {
|
||||
return str
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&/g, '&');
|
||||
},
|
||||
parseContent: function( msg ) {
|
||||
|
||||
//return [{type:"text",raw:msg}];
|
||||
|
||||
var output = [];
|
||||
|
||||
var tmp;
|
||||
var match = null;
|
||||
var index;
|
||||
var strUrlRegexp = "http[s]?://";
|
||||
var strEmailRegexp = "\\S+@\\S+\\.\\S+";
|
||||
var strSplitCounterR = "\\(\\d{1,2}\\/\\d{1,2}\\)$";
|
||||
var reAll = new RegExp("(?:^|[ \\n\\t.,:\\/?!])(#|@|" + strUrlRegexp + "|" + strEmailRegexp + "|" + strSplitCounterR + ")");
|
||||
var reHttp = new RegExp(strUrlRegexp);
|
||||
var reEmail = new RegExp(strEmailRegexp);
|
||||
var reSplitCounter = new RegExp(strSplitCounterR);
|
||||
|
||||
//msg = this.escapeHtmlEntities(msg);
|
||||
|
||||
while( msg != undefined && msg.length ) {
|
||||
|
||||
match = reAll.exec(msg);
|
||||
if( match ) {
|
||||
index = (match[0] === match[1]) ? match.index : match.index + 1;
|
||||
if( match[1] == "@" ) {
|
||||
output.push({type:"text",raw:(msg.substr(0, index))});
|
||||
tmp = msg.substr(index+1);
|
||||
var username = this.extractUsername(tmp);
|
||||
if( username.length ) {
|
||||
output.push({type:"mention",raw:"@"+username});
|
||||
}
|
||||
msg = tmp.substr(String(username).length);
|
||||
continue;
|
||||
}
|
||||
|
||||
if( reHttp.exec(match[1]) ) {
|
||||
output.push({type:"text",raw:(msg.substr(0, index))});
|
||||
tmp = msg.substring(index);
|
||||
var space = tmp.search(/[ \n\t]/);
|
||||
var url;
|
||||
if( space != -1 ) url = tmp.substring(0,space); else url = tmp;
|
||||
if( url.length ) {
|
||||
output.push({type:"url",raw:url});
|
||||
}
|
||||
msg = tmp.substr(String(url).length);
|
||||
continue;
|
||||
}
|
||||
|
||||
if( reEmail.exec(match[1]) ) {
|
||||
output.push({type:"text",raw:(msg.substr(0, index))});
|
||||
tmp = msg.substring(index);
|
||||
var space = tmp.search(/[ \n\t]/);
|
||||
var email;
|
||||
if( space != -1 ) email = tmp.substring(0,space); else email = tmp;
|
||||
if( email.length ) {
|
||||
output.push({type:"email",raw:email});
|
||||
}
|
||||
msg = tmp.substr(String(email).length);
|
||||
continue;
|
||||
}
|
||||
|
||||
if( match[1] == "#" ) {
|
||||
output.push({type:"text",raw:(msg.substr(0, index))});
|
||||
tmp = msg.substr(index+1);
|
||||
var hashtag = this.extractHashtag(tmp);
|
||||
if( hashtag.length ) {
|
||||
// var hashtag_lc='';
|
||||
// for( var i = 0; i < hashtag.length; i++ ) {
|
||||
// var c = hashtag[i];
|
||||
// hashtag_lc += (c >= 'A' && c <= 'Z') ? c.toLowerCase() : c;
|
||||
// }
|
||||
output.push({type:"hashtag",raw:"#"+hashtag});
|
||||
|
||||
}
|
||||
msg = tmp.substr(String(hashtag).length);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*if (reSplitCounter.exec(match[1])) {
|
||||
output.append({type:"text",raw:(msg.substr(0, index))});
|
||||
tmp = msg.substring(index);
|
||||
if( tmp.length ) {
|
||||
var splitCounter = $('<span class="splited-post-counter"></span>');
|
||||
splitCounter.text(tmp);
|
||||
output.append(splitCounter);
|
||||
msg = "";
|
||||
continue;
|
||||
}
|
||||
msg = tmp.substr(String(hashtag).length);
|
||||
continue;
|
||||
}*/
|
||||
}
|
||||
|
||||
output.push({type:"text",raw:this.reverseHtmlEntities(msg)});
|
||||
msg = "";
|
||||
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
},
|
||||
render: function() {
|
||||
|
||||
var parsedContent = this.parseContent(this.props.content);
|
||||
var parsedContent = PostContentHelper.parseContent(this.props.content);
|
||||
|
||||
//console.log(parsedContent)
|
||||
|
||||
|
@ -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 });
|
||||
}
|
||||
|
||||
});
|
@ -10,7 +10,7 @@ var React = require('react');
|
||||
|
||||
var SafeStateChangeMixin = require('../common/SafeStateChangeMixin.js');
|
||||
var SetIntervalMixin = require("../common/SetIntervalMixin.js");
|
||||
var TwistComposer = require("../common/TwistComposer.js");
|
||||
var PostComposer = require("../common/PostComposer.js");
|
||||
|
||||
module.exports = NewPostModalButton = React.createClass({
|
||||
getInitialState: function () {
|
||||
@ -23,10 +23,9 @@ module.exports = NewPostModalButton = React.createClass({
|
||||
isModalOpen: !this.state.isModalOpen
|
||||
});
|
||||
},
|
||||
handleNewPost: function (e) {
|
||||
e.preventDefault();
|
||||
//console.log(e)
|
||||
var msg = JSON.parse(JSON.stringify(e.target[0].value));
|
||||
handleNewPost: function (text) {
|
||||
console.log(text)
|
||||
var msg = text;
|
||||
if (!msg) {
|
||||
console.log("empty post was passed as new post")
|
||||
return;
|
||||
@ -39,38 +38,23 @@ module.exports = NewPostModalButton = React.createClass({
|
||||
window.dispatchEvent(event);
|
||||
|
||||
});
|
||||
|
||||
e.target[0].value = "";
|
||||
|
||||
|
||||
|
||||
this.handleToggle();
|
||||
|
||||
//React.findDOMNode(this.refs.msg).value = '';
|
||||
return;
|
||||
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={this.handleToggle} className="link-button-gray pull-right fullytight_all" bsStyle="link">
|
||||
<Glyphicon glyph='pencil' />
|
||||
<Modal title={<Glyphicon glyph='pencil'/>} show={this.state.isModalOpen} bsStyle='primary' onHide={this.handleToggle}>
|
||||
<div className='modal-body'>
|
||||
<PostComposer onSubmit={this.handleNewPost} />
|
||||
</div>
|
||||
</Modal>
|
||||
</Button>
|
||||
<Modal show={this.state.isModalOpen} bsStyle='primary' title={<Glyphicon glyph='pencil'/>} onRequestHide={this.handleToggle}>
|
||||
<div className='modal-body'>
|
||||
<form onSubmit={this.handleNewPost}>
|
||||
<Input type='textarea' label='Write Something' placeholder='textarea'/>
|
||||
<Input type='submit' value='Submit Post' data-dismiss="modal" />
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderOverlay: function() {
|
||||
|
||||
if (!this.state.isModalOpen) {
|
||||
return <span/>;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
});
|
@ -10,8 +10,7 @@
|
||||
"react-router": "*",
|
||||
"react-router-bootstrap": "*",
|
||||
"react-dom": "*",
|
||||
"selection-range": "*",
|
||||
"escape-html": "*",
|
||||
"react-wysiwyg": "*",
|
||||
"react-addons-css-transition-group": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -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…
x
Reference in New Issue
Block a user