Julian Steinwachs
9 years ago
11 changed files with 32151 additions and 30553 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@ -0,0 +1,197 @@
@@ -0,0 +1,197 @@
|
||||
var React = require('react'); |
||||
var ContentEditable = require('react-wysiwyg'); |
||||
var PostContentHelper = require('../common/PostContentHelper.js'); |
||||
var SafeStateChangeMixin = require('../common/SafeStateChangeMixin.js'); |
||||
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap') |
||||
, Button = ReactBootstrap.Button |
||||
, DropdownButton = ReactBootstrap.DropdownButton |
||||
, MenuItem = ReactBootstrap.MenuItem |
||||
, ButtonGroup = ReactBootstrap.ButtonGroup |
||||
, OverlayTrigger = ReactBootstrap.OverlayTrigger |
||||
, Popover = ReactBootstrap.Popover |
||||
, Glyphicon = ReactBootstrap.Glyphicon |
||||
, Grid = ReactBootstrap.Grid |
||||
, Col = ReactBootstrap.Col |
||||
, Row = ReactBootstrap.Row |
||||
|
||||
var escape = document.createElement('textarea') |
||||
|
||||
function escapeHTML(html) { |
||||
escape.textContent = html; |
||||
return escape.innerHTML; |
||||
} |
||||
|
||||
module.exports = PostComposer = React.createClass({ |
||||
mixins:[SafeStateChangeMixin], |
||||
getInitialState: function(){ |
||||
var editing = false |
||||
var defaultValue = '' |
||||
|
||||
return { |
||||
html: defaultValue, |
||||
editing: true, |
||||
placeholder: true, |
||||
maxLength: 140, |
||||
totalLength: defaultValue.length, |
||||
queryMention: false, |
||||
text: defaultValue |
||||
} |
||||
|
||||
}, |
||||
|
||||
componentDidMount: function () { |
||||
// Gives the window a callback to call before the next repaint.
|
||||
window.requestAnimationFrame(this.checkCursor) |
||||
}, |
||||
|
||||
checkCursor: function (timestamp) { |
||||
var self = this |
||||
var selection = window.getSelection() |
||||
|
||||
|
||||
if (this.state.editing && selection.focusNode) { |
||||
|
||||
var node = selection |
||||
.getRangeAt(0) |
||||
.commonAncestorContainer |
||||
.parentNode |
||||
|
||||
if (node.className === 'show-dropdown') { |
||||
// you could use the node to determine its position,
|
||||
// and show the dropdown inline, too.
|
||||
this.setStateSafe({ queryMention : node.textContent }) |
||||
} else if (this.state.queryMention) { |
||||
this.setStateSafe({ queryMention: false }) |
||||
} |
||||
|
||||
} else if (this.state.queryMention) { |
||||
this.setStateSafe({ queryMention: false }) |
||||
} |
||||
|
||||
window.requestAnimationFrame(self.checkCursor) |
||||
}, |
||||
|
||||
render: function(){ |
||||
|
||||
var isValid = (this.state.maxLength >= this.state.totalLength) |
||||
&& (this.state.totalLength > 0) |
||||
|
||||
return ( |
||||
<div> |
||||
<div>{this.state.error}</div> |
||||
<ContentEditable |
||||
ref='editable' |
||||
tagName='div' |
||||
html={this.state.html} |
||||
placeholder={this.state.placeholder} |
||||
placeholderText='write' |
||||
onKeyPress={this.onKeyPress} |
||||
preventStyling |
||||
noLinebreaks |
||||
onChange={this.onChange} |
||||
editing={this.state.editing} |
||||
style={{"outline": "none"}} |
||||
/> |
||||
<Row> |
||||
<Col xs={9} md={9}> |
||||
</Col> |
||||
<Col xs={1} md={1}> |
||||
<Button disabled id="content-length"> |
||||
{this.state.maxLength - this.state.totalLength} |
||||
</Button> |
||||
</Col> |
||||
<Col xs={2} md={2}> |
||||
<Button disabled={!isValid} onClick={this.handleSubmit}> |
||||
Twist |
||||
</Button> |
||||
</Col> |
||||
</Row> |
||||
</div> |
||||
); |
||||
/*<div> |
||||
Show autocomplete? {this.state.queryMention ? 'Yes ' + this.state.queryMention : 'No'} |
||||
</div>*/ |
||||
}, |
||||
|
||||
handleSubmit: function(){ |
||||
this.props.onSubmit(this.state.text); |
||||
}, |
||||
|
||||
autofocus: function () { |
||||
if (this.state.editing) { |
||||
this.refs.editable.autofocus() |
||||
} |
||||
}, |
||||
|
||||
onChange: function(text, setPlaceholder) { |
||||
// in order to render the updated html,
|
||||
// you need to pass it as a prop to contentEditable.
|
||||
// This gives you increased flexibility.
|
||||
if (setPlaceholder) { |
||||
this.setState({ |
||||
placeholder: true, |
||||
html: '', |
||||
totalLength: 0, |
||||
text: '' |
||||
}) |
||||
} else { |
||||
|
||||
var copy = text.slice(0, this.state.maxLength) |
||||
|
||||
var parsedContent = PostContentHelper.parseContent(copy); |
||||
|
||||
//console.log(copy,parsedContent);
|
||||
|
||||
var output = ""; |
||||
|
||||
parsedContent.map(function(item,index){ |
||||
//console.log(item.raw)
|
||||
switch(item.type) { |
||||
case "mention": |
||||
output+=('<a class="text-muted" href="#/profile/"'+item.raw.substr(1)+'">'+item.raw+'</a>'); |
||||
break; |
||||
case "hashtag": |
||||
output+=('<a class="text-muted" href="#/hashtag/"'+item.raw.substr(1)+'">'+item.raw+'</a>'); |
||||
break; |
||||
case "url": |
||||
output+=('<a class="text-primary" href="'+item.raw+'" target="_blank">'+item.raw+'</a>'); |
||||
break; |
||||
case "email": |
||||
output+=('<span class="text-primary">'+item.raw+'</span>'); |
||||
break; |
||||
default: |
||||
output+=('<span>'+item.raw+'</span>'); |
||||
} |
||||
}); |
||||
|
||||
// text overflow
|
||||
if (text.length > this.state.maxLength) { |
||||
var overflow = '<span style="text-decoration: line-through;">' + |
||||
text.slice(this.state.maxLength) + |
||||
'</span>' |
||||
output = output + overflow |
||||
} |
||||
|
||||
this.setState({ |
||||
placeholder: false, |
||||
html: output, |
||||
totalLength: text.length, |
||||
text: copy |
||||
}) |
||||
} |
||||
|
||||
}, |
||||
|
||||
enableEditing: function(){ |
||||
var editing = !this.state.editing |
||||
// set your contenteditable field into editing mode.
|
||||
this.setState({ editing: editing }); |
||||
if (editing) { |
||||
this.refs.editable.autofocus() |
||||
this.refs.editable.setCursorToEnd() |
||||
} |
||||
} |
||||
|
||||
}); |
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
module.exports = { |
||||
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}); |
||||
}else{ |
||||
output.push({type:"text",raw:"@"}); |
||||
} |
||||
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); |
||||
//console.log(tmp)
|
||||
var space = tmp.search(/\s/); |
||||
//console.log(space)
|
||||
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(/\s/); |
||||
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}); |
||||
|
||||
}else{ |
||||
output.push({type:"text",raw:"@"}); |
||||
} |
||||
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; |
||||
|
||||
} |
||||
} |
@ -0,0 +1,171 @@
@@ -0,0 +1,171 @@
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap') |
||||
, Button = ReactBootstrap.Button |
||||
, ButtonGroup = ReactBootstrap.ButtonGroup |
||||
, Glyphicon = ReactBootstrap.Glyphicon |
||||
, Modal = ReactBootstrap.Modal |
||||
, Input = ReactBootstrap.Input |
||||
|
||||
var React = require('react'); |
||||
|
||||
var SafeStateChangeMixin = require('../common/SafeStateChangeMixin.js'); |
||||
var SetIntervalMixin = require("../common/SetIntervalMixin.js"); |
||||
|
||||
module.exports = ExportAccountModalButton = React.createClass({ |
||||
mixins: [ |
||||
SafeStateChangeMixin |
||||
], |
||||
getInitialState: function () { |
||||
return { |
||||
isModalOpen: false, |
||||
useEncryption: true, |
||||
passphrase1: "", |
||||
passphrase2: "", |
||||
setupComplete: false, |
||||
encryptionInProgess: false, |
||||
encryptionComplete: false, |
||||
encryptedKey: "", |
||||
}; |
||||
}, |
||||
handleUseEncryptionChange: function(e) { |
||||
this.setState({ |
||||
useEncryption: e.target.checked, |
||||
encryptionInProgess: false, |
||||
encryptionComplete: false, |
||||
}); |
||||
}, |
||||
handlePassphrase1Change: function(e) { |
||||
this.setState({ |
||||
passphrase1: e.target.value, |
||||
encryptionInProgess: false, |
||||
encryptionComplete: false, |
||||
}); |
||||
}, |
||||
handlePassphrase2Change: function(e) { |
||||
this.setState({ |
||||
passphrase2: e.target.value, |
||||
encryptionInProgess: false, |
||||
encryptionComplete: false, |
||||
}); |
||||
}, |
||||
handleToggle: function () { |
||||
this.setState({ |
||||
isModalOpen: !this.state.isModalOpen |
||||
}); |
||||
}, |
||||
handleEncryption: function (e) { |
||||
|
||||
e.preventDefault(); |
||||
|
||||
var thisComponent = this; |
||||
|
||||
if(this.state.useEncryption){ |
||||
|
||||
var passphrase = this.state.passphrase1; |
||||
|
||||
thisComponent.setStateSafe({encryptionInProgess: true}); |
||||
|
||||
Twister.getAccount(this.props.username).encryptPrivateKey(passphrase, |
||||
function(encryptedKey){ |
||||
|
||||
thisComponent.setStateSafe({encryptedKey: encryptedKey, encryptionComplete: true, encryptionInProgess: false}); |
||||
|
||||
}); |
||||
|
||||
}else{ |
||||
|
||||
var wif = Twister.getAccount(this.props.username).getPrivateKey(); |
||||
|
||||
thisComponent.setStateSafe({encryptedKey: wif, encryptionComplete: true, encryptionInProgess: false}); |
||||
|
||||
} |
||||
|
||||
|
||||
return; |
||||
}, |
||||
render: function() { |
||||
|
||||
var belowForm = ( |
||||
<p/> |
||||
); |
||||
|
||||
if(this.state.encryptionInProgess){ |
||||
belowForm = ( |
||||
<p>Encryption in progress. Sorry for the lag.</p> |
||||
) |
||||
} |
||||
|
||||
if(this.state.encryptionComplete){ |
||||
if(this.state.useEncryption){ |
||||
|
||||
var formattedBody = "Your encrypted key for Twister: " |
||||
+this.state.encryptedKey |
||||
+"\n\nAdvice: Print this email and note down your username and passphrase on the same piece of paper."; |
||||
|
||||
var subject = "Encrypted Twister Private Key"; |
||||
|
||||
var mailToLink = "mailto:?body=" + encodeURIComponent(formattedBody) + "&subject=" + encodeURIComponent(subject); |
||||
|
||||
belowForm = ( |
||||
<p> |
||||
{"Your encrypted key: "+this.state.encryptedKey} |
||||
<Button href={mailToLink}>Send via Email</Button> |
||||
</p> |
||||
) |
||||
}else{ |
||||
belowForm = ( |
||||
<p> |
||||
{"Your private key: "+this.state.encryptedKey} |
||||
</p> |
||||
) |
||||
} |
||||
} |
||||
|
||||
var submitButton = ( |
||||
<Input type='submit' value='Encrypt Private Key' disabled={this.state.passphrase1!=this.state.passphrase2}/> |
||||
) |
||||
if(!this.state.useEncryption){ |
||||
submitButton = ( |
||||
<Input type='submit' value='Reveal Private Key'/> |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<Button onClick={this.handleToggle}> |
||||
Export Account |
||||
<Modal show={this.state.isModalOpen} bsStyle='primary' onHide={this.handleToggle}> |
||||
<Modal.Header> |
||||
<Glyphicon glyph='export'/> |
||||
</Modal.Header> |
||||
<Modal.Body> |
||||
<form onSubmit={this.handleEncryption}> |
||||
<Input |
||||
type='checkbox' |
||||
label='Use Encrpytion (highly recommended)' |
||||
checked={this.state.useEncryption} |
||||
onChange={this.handleUseEncryptionChange} |
||||
/> |
||||
<Input |
||||
type='password' |
||||
label='Passphrase' |
||||
value={this.state.passphrase1} |
||||
onChange={this.handlePassphrase1Change} |
||||
disabled={!this.state.useEncryption} |
||||
/> |
||||
<Input |
||||
type='password' |
||||
label='Repeat Passphrase' |
||||
value={this.state.passphrase2} |
||||
onChange={this.handlePassphrase2Change} |
||||
disabled={!this.state.useEncryption} |
||||
/> |
||||
{submitButton} |
||||
</form> |
||||
{belowForm} |
||||
|
||||
</Modal.Body> |
||||
</Modal> |
||||
</Button> |
||||
); |
||||
} |
||||
}); |
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
|
||||
var ReactBootstrap = require('react-bootstrap') |
||||
, Button = ReactBootstrap.Button |
||||
, ButtonGroup = ReactBootstrap.ButtonGroup |
||||
, Glyphicon = ReactBootstrap.Glyphicon |
||||
, Modal = ReactBootstrap.Modal |
||||
, Input = ReactBootstrap.Input |
||||
|
||||
var React = require('react'); |
||||
|
||||
var SafeStateChangeMixin = require('../common/SafeStateChangeMixin.js'); |
||||
var SetIntervalMixin = require("../common/SetIntervalMixin.js"); |
||||
|
||||
module.exports = GenerateAccountModalButton = React.createClass({ |
||||
mixins: [SafeStateChangeMixin], |
||||
getInitialState: function () { |
||||
return { |
||||
isModalOpen: false, |
||||
username: "", |
||||
checkedUsername: "", |
||||
available: false, |
||||
}; |
||||
}, |
||||
handleUsernameChange: function(e) { |
||||
this.setState({username: e.target.value}); |
||||
var thisComponent = this; |
||||
if(e.target.value.length){ |
||||
Twister.checkUsernameAvailable(e.target.value,function(result){ |
||||
thisComponent.setStateSafe({ |
||||
checkedUsername: e.target.value, |
||||
available: result |
||||
}); |
||||
}) |
||||
} |
||||
}, |
||||
handleToggle: function () { |
||||
this.setState({ |
||||
isModalOpen: !this.state.isModalOpen |
||||
}); |
||||
}, |
||||
handleGenerateAccount: function (e) { |
||||
|
||||
e.preventDefault(); |
||||
|
||||
var newusername = this.state.username; |
||||
|
||||
Twister.generateClientSideAccount(newusername,function(newaccount){ |
||||
|
||||
console.log(newaccount._name); |
||||
|
||||
var event = new CustomEvent('newaccountbyuser',{detail: newaccount}); |
||||
//alert("scrolled to bottom")
|
||||
window.dispatchEvent(event); |
||||
|
||||
}) |
||||
|
||||
this.handleToggle(); |
||||
|
||||
return; |
||||
}, |
||||
render: function() { |
||||
|
||||
var showAvailable = ( |
||||
<span/> |
||||
); |
||||
|
||||
if(this.state.username.length==0){ |
||||
|
||||
showAvailable = ( |
||||
<p>try a username</p> |
||||
); |
||||
|
||||
}else{ |
||||
if((this.state.username==this.state.checkedUsername)){ |
||||
|
||||
if(this.state.available){ |
||||
showAvailable = ( |
||||
<p>{this.state.username + " is available"}</p> |
||||
) |
||||
}else{ |
||||
showAvailable = ( |
||||
<p>{this.state.username + " is allready taken"}</p> |
||||
) |
||||
} |
||||
|
||||
}else{ |
||||
|
||||
showAvailable = ( |
||||
<p>checking ...</p> |
||||
); |
||||
|
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
return ( |
||||
<Button onClick={this.handleToggle}> |
||||
Generate Account |
||||
<Modal show={this.state.isModalOpen} bsStyle='primary' onHide={this.handleToggle}> |
||||
<Modal.Header> |
||||
<Glyphicon glyph='certificate'/> |
||||
</Modal.Header> |
||||
<Modal.Body> |
||||
<form onSubmit={this.handleGenerateAccount}> |
||||
<Input |
||||
type='text' |
||||
label='Username' |
||||
value={this.state.username} |
||||
onChange={this.handleUsernameChange} |
||||
/> |
||||
{showAvailable} |
||||
<Input type='submit' value='Generate Account' data-dismiss="modal" /> |
||||
</form> |
||||
</Modal.Body> |
||||
</Modal> |
||||
</Button> |
||||
); |
||||
} |
||||
}); |
Loading…
Reference in new issue