export key

This commit is contained in:
Julian Steinwachs 2016-01-27 09:13:32 +01:00
parent 44931065da
commit 8d2bc300ff
11 changed files with 4144 additions and 2546 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

BIN
fonts/passwordsP2P.pdf Normal file

Binary file not shown.

197
jsx/common/PostComposer.js Normal file
View File

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

View File

@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
},
reverseHtmlEntities: function(str) {
return str
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/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;
}
}

View File

@ -5,6 +5,7 @@ var EventListenerMixin = require('../common/EventListenerMixin.js');
var AppSettingsMixin = require('../common/AppSettingsMixin.js');
var ImportAccountModalButton = require('../other/ImportAccountModalButton.js');
var ExportAccountModalButton = require('../other/ExportAccountModalButton.js');
var GenerateAccountModalButton = require('../other/GenerateAccountModalButton.js');
var ReactBootstrap = require('react-bootstrap')
@ -40,7 +41,10 @@ module.exports = Accounts = React.createClass({
return (
<div key={"miniprofile:"+acc.name}>
<MiniProfile username={acc.name} pollIntervalProfile={thisComponent.props.pollIntervalProfile}/>
<p>{acc.status}</p>
<p>
{acc.status}
<ExportAccountModalButton username={acc.name}/>
</p>
</div>
);
})}

View File

@ -24,13 +24,10 @@ module.exports = Conversation = React.createClass({
SafeStateChangeMixin,
EventListenerMixin('newpostbyuser')
],
contextTypes: {
router: React.PropTypes.func
},
getInitialState: function() {
return {
username: this.context.router.getCurrentParams().username,
postid: parseInt(this.context.router.getCurrentParams().postid),
username: this.props.params.username,
postid: parseInt(this.props.params.postid),
data: [],
postIdentifiers: {},
loading: true

View File

@ -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>
);
}
});

View File

@ -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>
);
}
});

View File

@ -19,6 +19,7 @@
},
"scripts": {
"test": "jsx jsx build-buffer && node test/PosContentTest.js",
"watch": "watch \"npm run pull-lib-and-build\" jsx",
"build": "jsx jsx build-buffer && browserify build-buffer/App.js -o build/app-bundle.js",
"pull-lib-and-build": "cd ../twister-lib-js && npm run bundle && cd ../twister-react && cp ../twister-lib-js/twister-lib.js build && jsx jsx build-buffer && browserify build-buffer/App.js -o build/app-bundle.js"
},