A few changes, click for details

- broke post liking
- added more reliable replie
master
Arinerron 2018-09-29 20:13:30 -07:00
parent 04f89383f7
commit 1b193d098b
16 changed files with 891 additions and 72 deletions

View File

@ -23,6 +23,7 @@ from onionrblockapi import Block
import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions, onionrvalues import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions, onionrvalues
import onionrblacklist import onionrblacklist
import dbcreator import dbcreator
if sys.version_info < (3, 6): if sys.version_info < (3, 6):
try: try:
import sha3 import sha3
@ -35,6 +36,7 @@ class Core:
''' '''
Initialize Core Onionr library Initialize Core Onionr library
''' '''
try: try:
self.queueDB = 'data/queue.db' self.queueDB = 'data/queue.db'
self.peerDB = 'data/peers.db' self.peerDB = 'data/peers.db'
@ -86,7 +88,9 @@ class Core:
return return
def refreshFirstStartVars(self): def refreshFirstStartVars(self):
'''Hack to refresh some vars which may not be set on first start''' '''
Hack to refresh some vars which may not be set on first start
'''
if os.path.exists('data/hs/hostname'): if os.path.exists('data/hs/hostname'):
with open('data/hs/hostname', 'r') as hs: with open('data/hs/hostname', 'r') as hs:
self.hsAddress = hs.read().strip() self.hsAddress = hs.read().strip()
@ -95,6 +99,7 @@ class Core:
''' '''
Adds a public key to the key database (misleading function name) Adds a public key to the key database (misleading function name)
''' '''
# This function simply adds a peer to the DB # This function simply adds a peer to the DB
if not self._utils.validatePubKey(peerID): if not self._utils.validatePubKey(peerID):
return False return False
@ -126,6 +131,7 @@ class Core:
''' '''
Add an address to the address database (only tor currently) Add an address to the address database (only tor currently)
''' '''
if address == config.get('i2p.own_addr', None): if address == config.get('i2p.own_addr', None):
return False return False
@ -161,6 +167,7 @@ class Core:
''' '''
Remove an address from the address database Remove an address from the address database
''' '''
if self._utils.validateID(address): if self._utils.validateID(address):
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB)
c = conn.cursor() c = conn.cursor()
@ -180,6 +187,7 @@ class Core:
**You may want blacklist.addToDB(blockHash) **You may want blacklist.addToDB(blockHash)
''' '''
if self._utils.validateHash(block): if self._utils.validateHash(block):
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB)
c = conn.cursor() c = conn.cursor()
@ -204,18 +212,21 @@ class Core:
''' '''
Generate the address database Generate the address database
''' '''
self.dbCreate.createAddressDB() self.dbCreate.createAddressDB()
def createPeerDB(self): def createPeerDB(self):
''' '''
Generate the peer sqlite3 database and populate it with the peers table. Generate the peer sqlite3 database and populate it with the peers table.
''' '''
self.dbCreate.createPeerDB() self.dbCreate.createPeerDB()
def createBlockDB(self): def createBlockDB(self):
''' '''
Create a database for blocks Create a database for blocks
''' '''
self.dbCreate.createBlockDB() self.dbCreate.createBlockDB()
def addToBlockDB(self, newHash, selfInsert=False, dataSaved=False): def addToBlockDB(self, newHash, selfInsert=False, dataSaved=False):
@ -224,6 +235,7 @@ class Core:
Should be in hex format! Should be in hex format!
''' '''
if not os.path.exists(self.blockDB): if not os.path.exists(self.blockDB):
raise Exception('Block db does not exist') raise Exception('Block db does not exist')
if self._utils.hasBlock(newHash): if self._utils.hasBlock(newHash):
@ -246,6 +258,7 @@ class Core:
''' '''
Simply return the data associated to a hash Simply return the data associated to a hash
''' '''
try: try:
# logger.debug('Opening %s' % (str(self.blockDataLocation) + str(hash) + '.dat')) # logger.debug('Opening %s' % (str(self.blockDataLocation) + str(hash) + '.dat'))
dataFile = open(self.blockDataLocation + hash + '.dat', 'rb') dataFile = open(self.blockDataLocation + hash + '.dat', 'rb')
@ -268,6 +281,7 @@ class Core:
''' '''
Set the data assciated with a hash Set the data assciated with a hash
''' '''
data = data data = data
dataSize = sys.getsizeof(data) dataSize = sys.getsizeof(data)
@ -303,6 +317,7 @@ class Core:
''' '''
Encrypt the data directory on Onionr shutdown Encrypt the data directory on Onionr shutdown
''' '''
if os.path.exists('data.tar'): if os.path.exists('data.tar'):
os.remove('data.tar') os.remove('data.tar')
tar = tarfile.open("data.tar", "w") tar = tarfile.open("data.tar", "w")
@ -320,6 +335,7 @@ class Core:
''' '''
Decrypt the data directory on startup Decrypt the data directory on startup
''' '''
if not os.path.exists('data-encrypted.dat'): if not os.path.exists('data-encrypted.dat'):
return (False, 'encrypted archive does not exist') return (False, 'encrypted archive does not exist')
data = open('data-encrypted.dat', 'rb').read() data = open('data-encrypted.dat', 'rb').read()
@ -341,6 +357,7 @@ class Core:
This function intended to be used by the client. Queue to exchange data between "client" and server. This function intended to be used by the client. Queue to exchange data between "client" and server.
''' '''
retData = False retData = False
if not os.path.exists(self.queueDB): if not os.path.exists(self.queueDB):
self.makeDaemonDB() self.makeDaemonDB()
@ -364,25 +381,35 @@ class Core:
return retData return retData
def makeDaemonDB(self): def makeDaemonDB(self):
'''generate the daemon queue db''' '''
Generate the daemon queue db
'''
conn = sqlite3.connect(self.queueDB) conn = sqlite3.connect(self.queueDB)
c = conn.cursor() c = conn.cursor()
# Create table # Create table
c.execute('''CREATE TABLE commands c.execute('''CREATE TABLE commands
(id integer primary key autoincrement, command text, data text, date text)''') (id integer primary key autoincrement, command text, data text, date text)''')
conn.commit() conn.commit()
conn.close() conn.close()
return
def daemonQueueAdd(self, command, data=''): def daemonQueueAdd(self, command, data=''):
''' '''
Add a command to the daemon queue, used by the communication daemon (communicator.py) Add a command to the daemon queue, used by the communication daemon (communicator.py)
''' '''
retData = True retData = True
# Intended to be used by the web server # Intended to be used by the web server
date = self._utils.getEpoch() date = self._utils.getEpoch()
conn = sqlite3.connect(self.queueDB) conn = sqlite3.connect(self.queueDB)
c = conn.cursor() c = conn.cursor()
t = (command, data, date) t = (command, data, date)
try: try:
c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t) c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t)
conn.commit() conn.commit()
@ -398,13 +425,16 @@ class Core:
''' '''
Clear the daemon queue (somewhat dangerous) Clear the daemon queue (somewhat dangerous)
''' '''
conn = sqlite3.connect(self.queueDB) conn = sqlite3.connect(self.queueDB)
c = conn.cursor() c = conn.cursor()
try: try:
c.execute('DELETE FROM commands;') c.execute('DELETE FROM commands;')
conn.commit() conn.commit()
except: except:
pass pass
conn.close() conn.close()
events.event('queue_clear', onionr = None) events.event('queue_clear', onionr = None)
@ -543,13 +573,16 @@ class Core:
failure int 6 failure int 6
lastConnect 7 lastConnect 7
''' '''
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB)
c = conn.cursor() c = conn.cursor()
command = (address,) command = (address,)
infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6, 'lastConnect': 7} infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6, 'lastConnect': 7}
info = infoNumbers[info] info = infoNumbers[info]
iterCount = 0 iterCount = 0
retVal = '' retVal = ''
for row in c.execute('SELECT * FROM adders WHERE address=?;', command): for row in c.execute('SELECT * FROM adders WHERE address=?;', command):
for i in row: for i in row:
if iterCount == info: if iterCount == info:
@ -558,15 +591,19 @@ class Core:
else: else:
iterCount += 1 iterCount += 1
conn.close() conn.close()
return retVal return retVal
def setAddressInfo(self, address, key, data): def setAddressInfo(self, address, key, data):
''' '''
Update an address for a key Update an address for a key
''' '''
conn = sqlite3.connect(self.addressDB) conn = sqlite3.connect(self.addressDB)
c = conn.cursor() c = conn.cursor()
command = (data, address) command = (data, address)
# TODO: validate key on whitelist # TODO: validate key on whitelist
if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure', 'lastConnect', 'lastConnectAttempt'): if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure', 'lastConnect', 'lastConnectAttempt'):
raise Exception("Got invalid database key when setting address info") raise Exception("Got invalid database key when setting address info")
@ -574,18 +611,22 @@ class Core:
c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command) c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
conn.commit() conn.commit()
conn.close() conn.close()
return return
def getBlockList(self, unsaved = False): # TODO: Use unsaved?? def getBlockList(self, unsaved = False): # TODO: Use unsaved??
''' '''
Get list of our blocks Get list of our blocks
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB)
c = conn.cursor() c = conn.cursor()
if unsaved: if unsaved:
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();' execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
else: else:
execute = 'SELECT hash FROM hashes ORDER BY dateReceived ASC;' execute = 'SELECT hash FROM hashes ORDER BY dateReceived ASC;'
rows = list() rows = list()
for row in c.execute(execute): for row in c.execute(execute):
for i in row: for i in row:
@ -597,8 +638,10 @@ class Core:
''' '''
Returns the date a block was received Returns the date a block was received
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB)
c = conn.cursor() c = conn.cursor()
execute = 'SELECT dateReceived FROM hashes WHERE hash=?;' execute = 'SELECT dateReceived FROM hashes WHERE hash=?;'
args = (blockHash,) args = (blockHash,)
for row in c.execute(execute, args): for row in c.execute(execute, args):
@ -611,14 +654,18 @@ class Core:
''' '''
Returns a list of blocks by the type Returns a list of blocks by the type
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB)
c = conn.cursor() c = conn.cursor()
if orderDate: if orderDate:
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;' execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
else: else:
execute = 'SELECT hash FROM hashes WHERE dataType=?;' execute = 'SELECT hash FROM hashes WHERE dataType=?;'
args = (blockType,) args = (blockType,)
rows = list() rows = list()
for row in c.execute(execute, args): for row in c.execute(execute, args):
for i in row: for i in row:
rows.append(i) rows.append(i)
@ -670,6 +717,7 @@ class Core:
Inserts a block into the network Inserts a block into the network
encryptType must be specified to encrypt a block encryptType must be specified to encrypt a block
''' '''
retData = False retData = False
# check nonce # check nonce

View File

@ -72,6 +72,7 @@ class Block:
''' '''
Decrypt a block, loading decrypted data into their vars Decrypt a block, loading decrypted data into their vars
''' '''
if self.decrypted: if self.decrypted:
return True return True
retData = False retData = False
@ -104,6 +105,7 @@ class Block:
''' '''
Verify if a block's signature is signed by its claimed signer Verify if a block's signature is signed by its claimed signer
''' '''
core = self.getCore() core = self.getCore()
if core._crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True): if core._crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):

View File

@ -0,0 +1,31 @@
<!-- POST REPLIES -->
<div class="onionr-post-creator">
<div class="row">
<div class="onionr-reply-creator container">
<div class="row">
<div class="col-3">
<img class="onionr-post-creator-user-icon" id="onionr-reply-creator-user-icon">
</div>
<div class="col-9">
<div class="row">
<div class="col col-auto">
<a class="onionr-post-creator-user-name" id="onionr-reply-creator-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')"></a>
<a class="onionr-post-creator-user-id" id="onionr-reply-creator-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id"><$= LANG.REPLY_CREATOR_YOU $></a>
</div>
</div>
<textarea class="onionr-post-creator-content" id="onionr-reply-creator-content" oninput="replyCreatorChange()"></textarea>
<div class="onionr-post-creator-content-message" id="onionr-reply-creator-content-message"></div>
<input type="button" onclick="makeReply()" title="<$= LANG.REPLY_CREATOR_CREATE $>" value="<$= LANG.REPLY_CREATOR_CREATE $>" id="onionr-reply-creator-create" class="onionr-post-creator-create" />
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div id="onionr-replies"></div>
</div>
<!-- END POST REPLIES -->

View File

@ -0,0 +1,30 @@
<!-- POST FOCUS REPLIES -->
<div class="col-12">
<div class="row">
<div class="onionr-post-focus-reply-creator">
<div class="row">
<div class="col-1"></div>
<div class="col-2">
<img class="onionr-post-creator-user-icon" id="onionr-post-focus-reply-creator-user-icon">
</div>
<div class="col-9">
<div class="row">
<div class="col col-auto">
<a class="onionr-post-creator-user-name" id="onionr-post-focus-reply-creator-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')"></a>
<a class="onionr-post-creator-user-id" id="onionr-post-focus-reply-creator-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id"><$= LANG.REPLY_CREATOR_YOU $></a>
</div>
</div>
<textarea class="onionr-post-creator-content" id="onionr-post-focus-reply-creator-content" oninput="focusReplyCreatorChange()"></textarea>
<div class="onionr-post-creator-content-message" id="onionr-post-focus-reply-creator-content-message"></div>
<input type="button" onclick="makeFocusReply()" title="<$= LANG.REPLY_CREATOR_CREATE $>" value="<$= LANG.REPLY_CREATOR_CREATE $>" id="onionr-post-focus-reply-creator-create" class="onionr-post-creator-create" />
</div>
</div>
</div>
<div class="onionr-post-focus-replies"></div>
</div>
</div>
<!-- END POST FOCUS REPLIES -->

View File

@ -0,0 +1,31 @@
<!-- POST -->
<div class="col-12">
<div class="onionr-post" id="onionr-post-$post-hash" onclick="focusPost('$post-hash', 'user-id-url', 'user-name-url', '')">
<div class="row">
<div class="col-3">
<img class="onionr-post-user-icon" src="$user-image">
</div>
<div class="col-9">
<div class="row">
<div class="col col-auto">
<a class="onionr-post-user-name" id="onionr-post-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')">$user-name</a>
</div>
<div class="col col-auto text-right ml-auto pl-0">
<div class="onionr-post-date text-right" data-placement="top" data-toggle="tooltip" title="$date">$date-relative-truncated</div>
</div>
</div>
<div class="onionr-post-content">
$content
</div>
<div class="onionr-post-controls pt-2">
<a href="#!" onclick="toggleLike('$post-hash')" class="glyphicon glyphicon-heart mr-2">$liked</a>
<a href="#!" onclick="reply('$post-hash')" class="glyphicon glyphicon-comment mr-2"><$= LANG.POST_REPLY $></a>
</div>
</div>
</div>
</div>
</div>
<!-- END POST -->

View File

@ -37,6 +37,14 @@ body {
/* timeline */ /* timeline */
.onionr-post-focus-separator {
width: 100%;
padding: 1rem;
padding-left: 0;
padding-right: 0;
}
.onionr-post { .onionr-post {
padding: 1rem; padding: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;

View File

@ -5,6 +5,17 @@ body {
/* timeline */ /* timeline */
.onionr-post-focus-separator {
border-color: black;
}
.modal-content {
border: 1px solid black;
border-radius: 1rem;
background-color: lightgray;
}
.onionr-post { .onionr-post {
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;

View File

@ -73,7 +73,7 @@
</div> </div>
</div> </div>
<!-- POST --> <!-- POST CREATOR -->
<div class="col-12"> <div class="col-12">
<div class="onionr-post-creator"> <div class="onionr-post-creator">
<div class="row"> <div class="row">
@ -97,7 +97,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- END POST --> <!-- END POST CREATOR -->
</div> </div>
<div class="row" id="onionr-timeline-posts"> <div class="row" id="onionr-timeline-posts">
@ -108,29 +108,88 @@
<div class="d-none d-lg-block col-lg-3"> <div class="d-none d-lg-block col-lg-3">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="onionr-trending"> <div class="onionr-replies">
<h2>Trending</h2> <h2 id="onionr-replies-title"></h2>
</div> </div>
</div> </div>
<div id="onionr-reply-creator-panel">
</div>
</div>
<div class="row">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- onionr-post-focus --> <!-- POST FOCUS DIALOG -->
<div class="modal fade" id="onionr-post-focus" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true"> <div class="modal fade" id="onionr-post-focus" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="row p-3">
<div class="col-2">
<img src="" id="onionr-post-focus-user-icon" class="onionr-post-user-icon">
</div>
<div class="col-10">
<div class="row">
<div class="col col-auto">
<a class="onionr-post-user-name" id="onionr-post-focus-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url'); jQuery('#onionr-post-focus').modal('hide');">$user-name</a>
<a class="onionr-post-user-id" id="onionr-post-focus-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url'); jQuery('#onionr-post-focus').modal('hide');" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>
</div>
<div class="col col-auto text-right ml-auto pl-0">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body" id="onionr-post-focus-content"></div> </div>
<div class="onionr-post-content" id="onionr-post-focus-content">
</div>
</div>
<hr class="col-12 onionr-post-focus-separator" />
<!-- POST FOCUS REPLIES -->
<div class="col-12">
<div class="row">
<div class="onionr-post-focus-reply-creator">
<div class="row">
<div class="col-1"></div>
<div class="col-2">
<img class="onionr-post-creator-user-icon" id="onionr-post-focus-reply-creator-user-icon">
</div>
<div class="col-9">
<div class="row">
<div class="col col-auto">
<a class="onionr-post-creator-user-name" id="onionr-post-focus-reply-creator-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')"></a>
<a class="onionr-post-creator-user-id" id="onionr-post-focus-reply-creator-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id">you</a>
</div>
</div>
<textarea class="onionr-post-creator-content" id="onionr-post-focus-reply-creator-content" oninput="focusReplyCreatorChange()"></textarea>
<div class="onionr-post-creator-content-message" id="onionr-post-focus-reply-creator-content-message"></div>
<input type="button" onclick="makeFocusReply()" title="Reply" value="Reply" id="onionr-post-focus-reply-creator-create" class="onionr-post-creator-create" />
</div> </div>
</div> </div>
</div> </div>
<div class="onionr-post-focus-replies"></div>
</div>
</div>
<!-- END POST FOCUS REPLIES -->
</div>
</div>
</div>
</div>
<!-- END POST FOCUS DIALOG -->
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true"> <div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-dialog modal-dialog-centered" role="document">

View File

@ -239,7 +239,7 @@ class User {
} }
static getUser(id, callback) { static getUser(id, callback) {
console.log(callback); // console.log(callback);
var user = deserializeUser(id); var user = deserializeUser(id);
if(user === null) { if(user === null) {
Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) {
@ -256,7 +256,7 @@ class User {
user.setID(id); user.setID(id);
user.remember(); user.remember();
console.log(callback); // console.log(callback);
callback(user); callback(user);
return user; return user;
} }
@ -272,7 +272,7 @@ class User {
} }
}); });
} else { } else {
console.log(callback); // console.log(callback);
callback(user); callback(user);
return user; return user;
} }
@ -282,7 +282,39 @@ class User {
/* post class */ /* post class */
class Post { class Post {
/* returns the html content of a post */ /* returns the html content of a post */
getHTML() { getHTML(type) {
var replyTemplate = '<!-- POST -->\
<div class="col-12">\
<div class="onionr-post" id="onionr-post-$post-hash" onclick="focusPost(\'$post-hash\', \'user-id-url\', \'user-name-url\', \'\')">\
<div class="row">\
<div class="col-3">\
<img class="onionr-post-user-icon" src="$user-image">\
</div>\
<div class="col-9">\
<div class="row">\
<div class="col col-auto">\
<a class="onionr-post-user-name" id="onionr-post-user-name" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')">$user-name</a>\
</div>\
\
<div class="col col-auto text-right ml-auto pl-0">\
<div class="onionr-post-date text-right" data-placement="top" data-toggle="tooltip" title="$date">$date-relative-truncated</div>\
</div>\
</div>\
\
<div class="onionr-post-content">\
$content\
</div>\
\
<div class="onionr-post-controls pt-2">\
<a href="#!" onclick="toggleLike(\'$post-hash\')" class="glyphicon glyphicon-heart mr-2">$liked</a>\
<a href="#!" onclick="reply(\'$post-hash\')" class="glyphicon glyphicon-comment mr-2">reply</a>\
</div>\
</div>\
</div>\
</div>\
</div>\
<!-- END POST -->\
';
var postTemplate = '<!-- POST -->\ var postTemplate = '<!-- POST -->\
<div class="col-12">\ <div class="col-12">\
<div class="onionr-post" id="onionr-post-$post-hash" onclick="focusPost(\'$post-hash\', \'user-id-url\', \'user-name-url\', \'\')">\ <div class="onionr-post" id="onionr-post-$post-hash" onclick="focusPost(\'$post-hash\', \'user-id-url\', \'user-name-url\', \'\')">\
@ -317,29 +349,37 @@ class Post {
<!-- END POST -->\ <!-- END POST -->\
'; ';
var template = '';
if(type !== undefined && type !== null && type == 'reply')
template = replyTemplate;
else
template = postTemplate;
var device = (jQuery(document).width() < 768 ? 'mobile' : 'desktop'); var device = (jQuery(document).width() < 768 ? 'mobile' : 'desktop');
postTemplate = postTemplate.replaceAll('$user-name-url', Sanitize.html(Sanitize.url(this.getUser().getName()))); template = template.replaceAll('$user-name-url', Sanitize.html(Sanitize.url(this.getUser().getName())));
postTemplate = postTemplate.replaceAll('$user-name', Sanitize.html(this.getUser().getName())); template = template.replaceAll('$user-name', Sanitize.html(this.getUser().getName()));
postTemplate = postTemplate.replaceAll('$user-id-url', Sanitize.html(Sanitize.url(this.getUser().getID()))); template = template.replaceAll('$user-id-url', Sanitize.html(Sanitize.url(this.getUser().getID())));
postTemplate = postTemplate.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().substring(0, 12) + '...')); template = template.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().substring(0, 12) + '...'));
// postTemplate = postTemplate.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().split('-').slice(0, 4).join('-'))); // template = template.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().split('-').slice(0, 4).join('-')));
postTemplate = postTemplate.replaceAll('$user-id', Sanitize.html(this.getUser().getID())); template = template.replaceAll('$user-id', Sanitize.html(this.getUser().getID()));
postTemplate = postTemplate.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon())); template = template.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon()));
postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent()).replaceAll('\n', '<br />', 16)); // Maximum of 16 lines template = template.replaceAll('$content', Sanitize.html(this.getContent()).replaceAll('\n', '<br />', 16)); // Maximum of 16 lines
postTemplate = postTemplate.replaceAll('$post-hash', this.getHash()); template = template.replaceAll('$post-hash', this.getHash());
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : '')); template = template.replaceAll('$date-relative-truncated', timeSince(this.getPostDate(), 'mobile'));
postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString()); template = template.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : ''));
template = template.replaceAll('$date', this.getPostDate().toLocaleString());
if(this.getHash() in getPostMap() && getPostMap()[this.getHash()]['liked']) { if(this.getHash() in getPostMap() && getPostMap()[this.getHash()]['liked']) {
postTemplate = postTemplate.replaceAll('$liked', 'unlike'); template = template.replaceAll('$liked', 'unlike');
} else { } else {
postTemplate = postTemplate.replaceAll('$liked', 'like'); template = template.replaceAll('$liked', 'like');
} }
return postTemplate; return template;
} }
setUser(user) { setUser(user) {
@ -358,6 +398,14 @@ class Post {
return this.content; return this.content;
} }
setParent(parent) {
this.parent = parent;
}
getParent() {
return this.parent;
}
setPostDate(date) { // unix timestamp input setPostDate(date) { // unix timestamp input
if(date instanceof Date) if(date instanceof Date)
this.date = date; this.date = date;
@ -380,6 +428,9 @@ class Post {
save(callback) { save(callback) {
var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})}; var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})};
if(this.getParent() !== undefined && this.getParent() !== null)
args['parent'] = (this.getParent() instanceof Post ? this.getParent().getHash() : (this.getParent() instanceof Block ? this.getParent().getHash() : this.getParent()));
var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken());
console.log(url); console.log(url);
@ -458,8 +509,12 @@ class Block {
// returns the parent block's hash (not Block object, for performance) // returns the parent block's hash (not Block object, for performance)
getParent() { getParent() {
if(!(this.parent instanceof Block) && this.parent !== undefined && this.parent !== null) // console.log(this.parent);
this.parent = Block.openBlock(this.parent); // convert hash to Block object
// TODO: Create a function to fetch the block contents and parse it from the server; right now it is only possible to search for types of blocks (see Block.getBlocks), so it is impossible to return a Block object here
// if(!(this.parent instanceof Block) && this.parent !== undefined && this.parent !== null)
// this.parent = Block.openBlock(this.parent); // convert hash to Block object
return this.parent; return this.parent;
} }
@ -562,7 +617,7 @@ class Block {
// recreates a block by hash // recreates a block by hash
static openBlock(hash) { static openBlock(hash) {
return parseBlock(response); return Block.parseBlock(hash);
} }
// converts an associative array to a Block // converts an associative array to a Block

View File

@ -11,7 +11,7 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun
var blockContent = JSON.parse(block.getContent()); var blockContent = JSON.parse(block.getContent());
// just ignore anything shorter than 280 characters // just ignore anything shorter than 280 characters
if(String(blockContent['content']).length <= 280) { if(String(blockContent['content']).length <= 280 && block.getParent() === null) {
post.setContent(blockContent['content']); post.setContent(blockContent['content']);
post.setPostDate(block.getDate()); post.setPostDate(block.getDate());
post.setUser(user); post.setUser(user);
@ -104,6 +104,106 @@ function postCreatorChange() {
button.disabled = false; button.disabled = false;
} }
function replyCreatorChange() {
var content = document.getElementById('onionr-reply-creator-content').value;
var message = '';
var maxlength = 280;
var disable = true;
var warn = false;
if(content.length !== 0) {
if(content.length - content.replaceAll('\n', '').length > 16) {
// 16 max newlines
message = 'Please use less than 16 newlines';
} else if(content.length <= maxlength) {
// 280 max characters
message = '%s characters remaining'.replaceAll('%s', (280 - content.length));
disable = false;
if(maxlength - content.length < maxlength / 4) {
warn = true;
}
} else {
message = '%s characters over maximum'.replaceAll('%s', (content.length - maxlength));
}
}
var element = document.getElementById('onionr-reply-creator-content-message');
var button = document.getElementById("onionr-reply-creator-create");
if(message === '')
element.style.visibility = 'hidden';
else {
element.style.visibility = 'visible';
element.innerHTML = message;
if(disable)
element.style.color = 'red';
else if(warn)
element.style.color = '#FF8C00';
else
element.style.color = 'gray';
}
if(disable)
button.disabled = true;
else
button.disabled = false;
}
function focusReplyCreatorChange() {
var content = document.getElementById('onionr-post-focus-reply-creator-content').value;
var message = '';
var maxlength = 280;
var disable = true;
var warn = false;
if(content.length !== 0) {
if(content.length - content.replaceAll('\n', '').length > 16) {
// 16 max newlines
message = 'Please use less than 16 newlines';
} else if(content.length <= maxlength) {
// 280 max characters
message = '%s characters remaining'.replaceAll('%s', (280 - content.length));
disable = false;
if(maxlength - content.length < maxlength / 4) {
warn = true;
}
} else {
message = '%s characters over maximum'.replaceAll('%s', (content.length - maxlength));
}
}
var element = document.getElementById('onionr-post-focus-reply-creator-content-message');
var button = document.getElementById("onionr-post-focus-reply-creator-create");
if(message === '')
element.style.visibility = 'hidden';
else {
element.style.visibility = 'visible';
element.innerHTML = message;
if(disable)
element.style.color = 'red';
else if(warn)
element.style.color = '#FF8C00';
else
element.style.color = 'gray';
}
if(disable)
button.disabled = true;
else
button.disabled = false;
}
function viewProfile(id, name) { function viewProfile(id, name) {
id = decodeURIComponent(id); id = decodeURIComponent(id);
document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name)); document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name));
@ -179,12 +279,155 @@ function makePost() {
} }
} }
function getReplies(id, callback) {
Block.getBlocks({'type' : 'onionr-post', 'parent' : id, 'signed' : true, 'reverse' : true}, callback);
}
function focusPost(id) { function focusPost(id) {
document.getElementById("onionr-post-focus-content").innerHTML = ""; viewReplies(id);
}
function viewRepliesMobile(id) {
var post = document.getElementById('onionr-post-' + id);
var user_name = '';
var user_id = '';
var user_id_trunc = '';
var user_icon = '';
var post_content = '';
if(post !== null && post !== undefined) {
// if the post is in the timeline, get the data from it
user_name = post.getElementsByClassName('onionr-post-user-name')[0].innerHTML;
user_id = post.getElementsByClassName('onionr-post-user-id')[0].title;
user_id_trunc = post.getElementsByClassName('onionr-post-user-id')[0].innerHTML;
user_icon = post.getElementsByClassName('onionr-post-user-icon')[0].src;
post_content = post.getElementsByClassName('onionr-post-content')[0].innerHTML;
} else {
// otherwise, fetch the data
}
document.getElementById('onionr-post-focus-user-icon').src = user_icon;
document.getElementById('onionr-post-focus-user-name').innerHTML = user_name;
document.getElementById('onionr-post-focus-user-id').innerHTML = user_id_trunc;
document.getElementById('onionr-post-focus-user-id').title = user_id;
document.getElementById('onionr-post-focus-content').innerHTML = post_content;
document.getElementById('onionr-post-focus-reply-creator-user-name').innerHTML = Sanitize.html(Sanitize.username(getCurrentUser().getName()));
document.getElementById('onionr-post-focus-reply-creator-user-icon').src = "data:image/jpeg;base64," + Sanitize.html(getCurrentUser().getIcon());
document.getElementById('onionr-post-focus-reply-creator-content').value = '';
document.getElementById('onionr-post-focus-reply-creator-content-message').value = '';
jQuery('#onionr-post-focus').modal('show'); jQuery('#onionr-post-focus').modal('show');
} }
function viewReplies(id) {
document.getElementById('onionr-replies-title').innerHTML = 'Replies';
document.getElementById('onionr-reply-creator-panel').originalPost = id;
document.getElementById('onionr-reply-creator-panel').innerHTML = '<!-- POST REPLIES -->\
<div class="onionr-post-creator">\
<div class="row">\
<div class="onionr-reply-creator container">\
<div class="row">\
<div class="col-3">\
<img class="onionr-post-creator-user-icon" id="onionr-reply-creator-user-icon">\
</div>\
<div class="col-9">\
<div class="row">\
<div class="col col-auto">\
<a class="onionr-post-creator-user-name" id="onionr-reply-creator-user-name" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')"></a>\
<a class="onionr-post-creator-user-id" id="onionr-reply-creator-user-id" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')" data-placement="top" data-toggle="tooltip" title="$user-id">you</a>\
</div>\
</div>\
\
<textarea class="onionr-post-creator-content" id="onionr-reply-creator-content" oninput="replyCreatorChange()"></textarea>\
\
<div class="onionr-post-creator-content-message" id="onionr-reply-creator-content-message"></div>\
\
<input type="button" onclick="makeReply()" title="Reply" value="Reply" id="onionr-reply-creator-create" class="onionr-post-creator-create" />\
</div>\
</div>\
</div>\
</div>\
</div>\
\
<div class="row">\
<div id="onionr-replies"></div>\
</div>\
<!-- END POST REPLIES -->\
';
document.getElementById('onionr-reply-creator-content').innerHTML = '';
document.getElementById("onionr-reply-creator-content").placeholder = "Enter a message here...";
document.getElementById('onionr-reply-creator-user-name').innerHTML = Sanitize.html(Sanitize.username(getCurrentUser().getName()));
document.getElementById('onionr-reply-creator-user-icon').src = "data:image/jpeg;base64," + Sanitize.html(getCurrentUser().getIcon());
document.getElementById('onionr-replies').innerHTML = '';
getReplies(id, function(data) {
var replies = document.getElementById('onionr-replies');
replies.innerHTML = '';
for(var i = 0; i < data.length; i++) {
try {
var block = data[i];
var finished = false;
User.getUser(new String(block.getHeader('signer', 'unknown')), function(user) {
var post = new Post();
var blockContent = JSON.parse(block.getContent());
// just ignore anything shorter than 280 characters
if(String(blockContent['content']).length <= 280) {
post.setContent(blockContent['content']);
post.setPostDate(block.getDate());
post.setUser(user);
post.setHash(block.getHash());
replies.innerHTML += post.getHTML('reply');
}
finished = true;
});
while(!finished);
} catch(e) {
console.log('Troublemaker block: ' + data[i].getHash());
console.log(e);
}
}
});
}
function makeReply() {
var content = document.getElementById("onionr-reply-creator-content").value;
if(content.trim() !== '') {
var post = new Post();
var originalPost = document.getElementById('onionr-reply-creator-panel').originalPost;
console.log('Original post hash: ' + originalPost);
post.setUser(getCurrentUser());
post.setParent(originalPost);
post.setContent(content);
post.setPostDate(new Date());
post.save(function(data) {}); // async, but no function
document.getElementById('onionr-replies').innerHTML = post.getHTML('reply') + document.getElementById('onionr-replies').innerHTML;
document.getElementById("onionr-reply-creator-content").value = "";
document.getElementById("onionr-reply-creator-content").focus();
replyCreatorChange();
} else {
console.log('Not making empty reply.');
}
}
jQuery('body').on('click', '[data-editable]', function() { jQuery('body').on('click', '[data-editable]', function() {
var el = jQuery(this); var el = jQuery(this);
var txt = el.text(); var txt = el.text();
@ -220,9 +463,9 @@ jQuery('body').on('click', '[data-editable]', function() {
input.one('blur', save).bind('keyup', saveEnter).focus(); input.one('blur', save).bind('keyup', saveEnter).focus();
}); });
//viewProfile('$user-id-url', '$user-name-url') //viewProfile('$user-id-url', '$user-name-url')
jQuery('#onionr-post-user-id').on('click', function(e) { alert(3);}); // jQuery('#onionr-post-user-id').on('click', function(e) { alert(3);});
//jQuery('#onionr-post *').on('click', function(e) { e.stopPropagation(); }); //jQuery('#onionr-post *').on('click', function(e) { e.stopPropagation(); });
jQuery('#onionr-post').click(function(e) { alert(1); }); // jQuery('#onionr-post').click(function(e) { alert(1); });
currentUser = getCurrentUser(); currentUser = getCurrentUser();
if(currentUser !== undefined && currentUser !== null) { if(currentUser !== undefined && currentUser !== null) {
@ -230,7 +473,11 @@ if(currentUser !== undefined && currentUser !== null) {
document.getElementById("onionr-post-creator-user-id").innerHTML = "you"; document.getElementById("onionr-post-creator-user-id").innerHTML = "you";
document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon());
document.getElementById("onionr-post-creator-user-id").title = currentUser.getID(); document.getElementById("onionr-post-creator-user-id").title = currentUser.getID();
document.getElementById("onionr-post-creator-content").placeholder = "Enter a message here..."; document.getElementById("onionr-post-creator-content").placeholder = "Enter a message here...";
document.getElementById("onionr-post-focus-reply-creator-content").placeholder = "Enter a message here...";
document.getElementById("onionr-post-focus-reply-creator-user-id").innerHTML = "you";
} }
viewCurrentProfile = function() { viewCurrentProfile = function() {

View File

@ -8,6 +8,7 @@
"LATEST" : "Latest...", "LATEST" : "Latest...",
"TRENDING" : "Trending", "TRENDING" : "Trending",
"REPLIES" : "Replies",
"MODAL_TITLE" : "Loading...", "MODAL_TITLE" : "Loading...",
"MODAL_MESSAGE" : "Onionr has begun performing a CPU-intensive operation. If this operation does not complete in the next 10 seconds, try reloading the page.", "MODAL_MESSAGE" : "Onionr has begun performing a CPU-intensive operation. If this operation does not complete in the next 10 seconds, try reloading the page.",
@ -20,10 +21,18 @@
"POST_CREATOR_PLACEHOLDER" : "Enter a message here...", "POST_CREATOR_PLACEHOLDER" : "Enter a message here...",
"POST_CREATOR_CREATE" : "Create post", "POST_CREATOR_CREATE" : "Create post",
"REPLY_CREATOR_YOU" : "you",
"REPLY_CREATOR_PLACEHOLDER" : "Enter reply here...",
"REPLY_CREATOR_CREATE" : "Reply",
"POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES" : "Please use less than 16 newlines", "POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES" : "Please use less than 16 newlines",
"POST_CREATOR_MESSAGE_REMAINING" : "%s characters remaining", "POST_CREATOR_MESSAGE_REMAINING" : "%s characters remaining",
"POST_CREATOR_MESSAGE_OVER" : "%s characters over maximum", "POST_CREATOR_MESSAGE_OVER" : "%s characters over maximum",
"REPLY_CREATOR_MESSAGE_MAXIMUM_NEWLINES" : "Please use less than 16 newlines",
"REPLY_CREATOR_MESSAGE_REMAINING" : "%s characters remaining",
"REPLY_CREATOR_MESSAGE_OVER" : "%s characters over maximum",
"PROFILE_EDIT_SAVE" : "Save", "PROFILE_EDIT_SAVE" : "Save",
"PROFILE_EDIT_CANCEL" : "Cancel" "PROFILE_EDIT_CANCEL" : "Cancel"
}, },

View File

@ -37,6 +37,14 @@ body {
/* timeline */ /* timeline */
.onionr-post-focus-separator {
width: 100%;
padding: 1rem;
padding-left: 0;
padding-right: 0;
}
.onionr-post { .onionr-post {
padding: 1rem; padding: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;

View File

@ -5,6 +5,17 @@ body {
/* timeline */ /* timeline */
.onionr-post-focus-separator {
border-color: black;
}
.modal-content {
border: 1px solid black;
border-radius: 1rem;
background-color: lightgray;
}
.onionr-post { .onionr-post {
border: 1px solid black; border: 1px solid black;
border-radius: 1rem; border-radius: 1rem;

View File

@ -43,7 +43,7 @@
</div> </div>
</div> </div>
<!-- POST --> <!-- POST CREATOR -->
<div class="col-12"> <div class="col-12">
<div class="onionr-post-creator"> <div class="onionr-post-creator">
<div class="row"> <div class="row">
@ -67,7 +67,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- END POST --> <!-- END POST CREATOR -->
</div> </div>
<div class="row" id="onionr-timeline-posts"> <div class="row" id="onionr-timeline-posts">
@ -78,28 +78,57 @@
<div class="d-none d-lg-block col-lg-3"> <div class="d-none d-lg-block col-lg-3">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="onionr-trending"> <div class="onionr-replies">
<h2><$= LANG.TRENDING $></h2> <h2 id="onionr-replies-title"></h2>
</div> </div>
</div> </div>
<div id="onionr-reply-creator-panel">
</div>
</div>
<div class="row">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- onionr-post-focus --> <!-- POST FOCUS DIALOG -->
<div class="modal fade" id="onionr-post-focus" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true"> <div class="modal fade" id="onionr-post-focus" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="row p-3">
<div class="col-2">
<img src="" id="onionr-post-focus-user-icon" class="onionr-post-user-icon">
</div>
<div class="col-10">
<div class="row">
<div class="col col-auto">
<a class="onionr-post-user-name" id="onionr-post-focus-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url'); jQuery('#onionr-post-focus').modal('hide');">$user-name</a>
<a class="onionr-post-user-id" id="onionr-post-focus-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url'); jQuery('#onionr-post-focus').modal('hide');" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>
</div>
<div class="col col-auto text-right ml-auto pl-0">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body" id="onionr-post-focus-content"></div> </div>
<div class="onionr-post-content" id="onionr-post-focus-content">
</div>
</div>
<hr class="col-12 onionr-post-focus-separator" />
<$= htmlTemplate('onionr-timeline-reply-creator') $>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- END POST FOCUS DIALOG -->
<footer /> <footer />
<script src="js/timeline.js"></script> <script src="js/timeline.js"></script>

View File

@ -239,7 +239,7 @@ class User {
} }
static getUser(id, callback) { static getUser(id, callback) {
console.log(callback); // console.log(callback);
var user = deserializeUser(id); var user = deserializeUser(id);
if(user === null) { if(user === null) {
Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) { Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) {
@ -256,7 +256,7 @@ class User {
user.setID(id); user.setID(id);
user.remember(); user.remember();
console.log(callback); // console.log(callback);
callback(user); callback(user);
return user; return user;
} }
@ -272,7 +272,7 @@ class User {
} }
}); });
} else { } else {
console.log(callback); // console.log(callback);
callback(user); callback(user);
return user; return user;
} }
@ -282,32 +282,41 @@ class User {
/* post class */ /* post class */
class Post { class Post {
/* returns the html content of a post */ /* returns the html content of a post */
getHTML() { getHTML(type) {
var replyTemplate = '<$= jsTemplate('onionr-timeline-reply') $>';
var postTemplate = '<$= jsTemplate('onionr-timeline-post') $>'; var postTemplate = '<$= jsTemplate('onionr-timeline-post') $>';
var template = '';
if(type !== undefined && type !== null && type == 'reply')
template = replyTemplate;
else
template = postTemplate;
var device = (jQuery(document).width() < 768 ? 'mobile' : 'desktop'); var device = (jQuery(document).width() < 768 ? 'mobile' : 'desktop');
postTemplate = postTemplate.replaceAll('$user-name-url', Sanitize.html(Sanitize.url(this.getUser().getName()))); template = template.replaceAll('$user-name-url', Sanitize.html(Sanitize.url(this.getUser().getName())));
postTemplate = postTemplate.replaceAll('$user-name', Sanitize.html(this.getUser().getName())); template = template.replaceAll('$user-name', Sanitize.html(this.getUser().getName()));
postTemplate = postTemplate.replaceAll('$user-id-url', Sanitize.html(Sanitize.url(this.getUser().getID()))); template = template.replaceAll('$user-id-url', Sanitize.html(Sanitize.url(this.getUser().getID())));
postTemplate = postTemplate.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().substring(0, 12) + '...')); template = template.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().substring(0, 12) + '...'));
// postTemplate = postTemplate.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().split('-').slice(0, 4).join('-'))); // template = template.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().split('-').slice(0, 4).join('-')));
postTemplate = postTemplate.replaceAll('$user-id', Sanitize.html(this.getUser().getID())); template = template.replaceAll('$user-id', Sanitize.html(this.getUser().getID()));
postTemplate = postTemplate.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon())); template = template.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon()));
postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent()).replaceAll('\n', '<br />', 16)); // Maximum of 16 lines template = template.replaceAll('$content', Sanitize.html(this.getContent()).replaceAll('\n', '<br />', 16)); // Maximum of 16 lines
postTemplate = postTemplate.replaceAll('$post-hash', this.getHash()); template = template.replaceAll('$post-hash', this.getHash());
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : '')); template = template.replaceAll('$date-relative-truncated', timeSince(this.getPostDate(), 'mobile'));
postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString()); template = template.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : ''));
template = template.replaceAll('$date', this.getPostDate().toLocaleString());
if(this.getHash() in getPostMap() && getPostMap()[this.getHash()]['liked']) { if(this.getHash() in getPostMap() && getPostMap()[this.getHash()]['liked']) {
postTemplate = postTemplate.replaceAll('$liked', '<$= LANG.POST_UNLIKE $>'); template = template.replaceAll('$liked', '<$= LANG.POST_UNLIKE $>');
} else { } else {
postTemplate = postTemplate.replaceAll('$liked', '<$= LANG.POST_LIKE $>'); template = template.replaceAll('$liked', '<$= LANG.POST_LIKE $>');
} }
return postTemplate; return template;
} }
setUser(user) { setUser(user) {
@ -326,6 +335,14 @@ class Post {
return this.content; return this.content;
} }
setParent(parent) {
this.parent = parent;
}
getParent() {
return this.parent;
}
setPostDate(date) { // unix timestamp input setPostDate(date) { // unix timestamp input
if(date instanceof Date) if(date instanceof Date)
this.date = date; this.date = date;
@ -348,6 +365,9 @@ class Post {
save(callback) { save(callback) {
var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})}; var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})};
if(this.getParent() !== undefined && this.getParent() !== null)
args['parent'] = (this.getParent() instanceof Post ? this.getParent().getHash() : (this.getParent() instanceof Block ? this.getParent().getHash() : this.getParent()));
var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken()); var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken());
console.log(url); console.log(url);
@ -426,8 +446,12 @@ class Block {
// returns the parent block's hash (not Block object, for performance) // returns the parent block's hash (not Block object, for performance)
getParent() { getParent() {
if(!(this.parent instanceof Block) && this.parent !== undefined && this.parent !== null) // console.log(this.parent);
this.parent = Block.openBlock(this.parent); // convert hash to Block object
// TODO: Create a function to fetch the block contents and parse it from the server; right now it is only possible to search for types of blocks (see Block.getBlocks), so it is impossible to return a Block object here
// if(!(this.parent instanceof Block) && this.parent !== undefined && this.parent !== null)
// this.parent = Block.openBlock(this.parent); // convert hash to Block object
return this.parent; return this.parent;
} }
@ -530,7 +554,7 @@ class Block {
// recreates a block by hash // recreates a block by hash
static openBlock(hash) { static openBlock(hash) {
return parseBlock(response); return Block.parseBlock(hash);
} }
// converts an associative array to a Block // converts an associative array to a Block

View File

@ -11,7 +11,7 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun
var blockContent = JSON.parse(block.getContent()); var blockContent = JSON.parse(block.getContent());
// just ignore anything shorter than 280 characters // just ignore anything shorter than 280 characters
if(String(blockContent['content']).length <= 280) { if(String(blockContent['content']).length <= 280 && block.getParent() === null) {
post.setContent(blockContent['content']); post.setContent(blockContent['content']);
post.setPostDate(block.getDate()); post.setPostDate(block.getDate());
post.setUser(user); post.setUser(user);
@ -104,6 +104,106 @@ function postCreatorChange() {
button.disabled = false; button.disabled = false;
} }
function replyCreatorChange() {
var content = document.getElementById('onionr-reply-creator-content').value;
var message = '';
var maxlength = 280;
var disable = true;
var warn = false;
if(content.length !== 0) {
if(content.length - content.replaceAll('\n', '').length > 16) {
// 16 max newlines
message = '<$= LANG.POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES $>';
} else if(content.length <= maxlength) {
// 280 max characters
message = '<$= LANG.POST_CREATOR_MESSAGE_REMAINING $>'.replaceAll('%s', (280 - content.length));
disable = false;
if(maxlength - content.length < maxlength / 4) {
warn = true;
}
} else {
message = '<$= LANG.POST_CREATOR_MESSAGE_OVER $>'.replaceAll('%s', (content.length - maxlength));
}
}
var element = document.getElementById('onionr-reply-creator-content-message');
var button = document.getElementById("onionr-reply-creator-create");
if(message === '')
element.style.visibility = 'hidden';
else {
element.style.visibility = 'visible';
element.innerHTML = message;
if(disable)
element.style.color = 'red';
else if(warn)
element.style.color = '#FF8C00';
else
element.style.color = 'gray';
}
if(disable)
button.disabled = true;
else
button.disabled = false;
}
function focusReplyCreatorChange() {
var content = document.getElementById('onionr-post-focus-reply-creator-content').value;
var message = '';
var maxlength = 280;
var disable = true;
var warn = false;
if(content.length !== 0) {
if(content.length - content.replaceAll('\n', '').length > 16) {
// 16 max newlines
message = '<$= LANG.POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES $>';
} else if(content.length <= maxlength) {
// 280 max characters
message = '<$= LANG.POST_CREATOR_MESSAGE_REMAINING $>'.replaceAll('%s', (280 - content.length));
disable = false;
if(maxlength - content.length < maxlength / 4) {
warn = true;
}
} else {
message = '<$= LANG.POST_CREATOR_MESSAGE_OVER $>'.replaceAll('%s', (content.length - maxlength));
}
}
var element = document.getElementById('onionr-post-focus-reply-creator-content-message');
var button = document.getElementById("onionr-post-focus-reply-creator-create");
if(message === '')
element.style.visibility = 'hidden';
else {
element.style.visibility = 'visible';
element.innerHTML = message;
if(disable)
element.style.color = 'red';
else if(warn)
element.style.color = '#FF8C00';
else
element.style.color = 'gray';
}
if(disable)
button.disabled = true;
else
button.disabled = false;
}
function viewProfile(id, name) { function viewProfile(id, name) {
id = decodeURIComponent(id); id = decodeURIComponent(id);
document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name)); document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name));
@ -179,12 +279,124 @@ function makePost() {
} }
} }
function getReplies(id, callback) {
Block.getBlocks({'type' : 'onionr-post', 'parent' : id, 'signed' : true, 'reverse' : true}, callback);
}
function focusPost(id) { function focusPost(id) {
document.getElementById("onionr-post-focus-content").innerHTML = ""; viewReplies(id);
}
function viewRepliesMobile(id) {
var post = document.getElementById('onionr-post-' + id);
var user_name = '';
var user_id = '';
var user_id_trunc = '';
var user_icon = '';
var post_content = '';
if(post !== null && post !== undefined) {
// if the post is in the timeline, get the data from it
user_name = post.getElementsByClassName('onionr-post-user-name')[0].innerHTML;
user_id = post.getElementsByClassName('onionr-post-user-id')[0].title;
user_id_trunc = post.getElementsByClassName('onionr-post-user-id')[0].innerHTML;
user_icon = post.getElementsByClassName('onionr-post-user-icon')[0].src;
post_content = post.getElementsByClassName('onionr-post-content')[0].innerHTML;
} else {
// otherwise, fetch the data
}
document.getElementById('onionr-post-focus-user-icon').src = user_icon;
document.getElementById('onionr-post-focus-user-name').innerHTML = user_name;
document.getElementById('onionr-post-focus-user-id').innerHTML = user_id_trunc;
document.getElementById('onionr-post-focus-user-id').title = user_id;
document.getElementById('onionr-post-focus-content').innerHTML = post_content;
document.getElementById('onionr-post-focus-reply-creator-user-name').innerHTML = Sanitize.html(Sanitize.username(getCurrentUser().getName()));
document.getElementById('onionr-post-focus-reply-creator-user-icon').src = "data:image/jpeg;base64," + Sanitize.html(getCurrentUser().getIcon());
document.getElementById('onionr-post-focus-reply-creator-content').value = '';
document.getElementById('onionr-post-focus-reply-creator-content-message').value = '';
jQuery('#onionr-post-focus').modal('show'); jQuery('#onionr-post-focus').modal('show');
} }
function viewReplies(id) {
document.getElementById('onionr-replies-title').innerHTML = '<$= LANG.REPLIES $>';
document.getElementById('onionr-reply-creator-panel').originalPost = id;
document.getElementById('onionr-reply-creator-panel').innerHTML = '<$= jsTemplate('onionr-reply-creator') $>';
document.getElementById('onionr-reply-creator-content').innerHTML = '';
document.getElementById("onionr-reply-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>";
document.getElementById('onionr-reply-creator-user-name').innerHTML = Sanitize.html(Sanitize.username(getCurrentUser().getName()));
document.getElementById('onionr-reply-creator-user-icon').src = "data:image/jpeg;base64," + Sanitize.html(getCurrentUser().getIcon());
document.getElementById('onionr-replies').innerHTML = '';
getReplies(id, function(data) {
var replies = document.getElementById('onionr-replies');
replies.innerHTML = '';
for(var i = 0; i < data.length; i++) {
try {
var block = data[i];
var finished = false;
User.getUser(new String(block.getHeader('signer', 'unknown')), function(user) {
var post = new Post();
var blockContent = JSON.parse(block.getContent());
// just ignore anything shorter than 280 characters
if(String(blockContent['content']).length <= 280) {
post.setContent(blockContent['content']);
post.setPostDate(block.getDate());
post.setUser(user);
post.setHash(block.getHash());
replies.innerHTML += post.getHTML('reply');
}
finished = true;
});
while(!finished);
} catch(e) {
console.log('Troublemaker block: ' + data[i].getHash());
console.log(e);
}
}
});
}
function makeReply() {
var content = document.getElementById("onionr-reply-creator-content").value;
if(content.trim() !== '') {
var post = new Post();
var originalPost = document.getElementById('onionr-reply-creator-panel').originalPost;
console.log('Original post hash: ' + originalPost);
post.setUser(getCurrentUser());
post.setParent(originalPost);
post.setContent(content);
post.setPostDate(new Date());
post.save(function(data) {}); // async, but no function
document.getElementById('onionr-replies').innerHTML = post.getHTML('reply') + document.getElementById('onionr-replies').innerHTML;
document.getElementById("onionr-reply-creator-content").value = "";
document.getElementById("onionr-reply-creator-content").focus();
replyCreatorChange();
} else {
console.log('Not making empty reply.');
}
}
jQuery('body').on('click', '[data-editable]', function() { jQuery('body').on('click', '[data-editable]', function() {
var el = jQuery(this); var el = jQuery(this);
var txt = el.text(); var txt = el.text();
@ -220,9 +432,9 @@ jQuery('body').on('click', '[data-editable]', function() {
input.one('blur', save).bind('keyup', saveEnter).focus(); input.one('blur', save).bind('keyup', saveEnter).focus();
}); });
//viewProfile('$user-id-url', '$user-name-url') //viewProfile('$user-id-url', '$user-name-url')
jQuery('#onionr-post-user-id').on('click', function(e) { alert(3);}); // jQuery('#onionr-post-user-id').on('click', function(e) { alert(3);});
//jQuery('#onionr-post *').on('click', function(e) { e.stopPropagation(); }); //jQuery('#onionr-post *').on('click', function(e) { e.stopPropagation(); });
jQuery('#onionr-post').click(function(e) { alert(1); }); // jQuery('#onionr-post').click(function(e) { alert(1); });
currentUser = getCurrentUser(); currentUser = getCurrentUser();
if(currentUser !== undefined && currentUser !== null) { if(currentUser !== undefined && currentUser !== null) {
@ -230,7 +442,11 @@ if(currentUser !== undefined && currentUser !== null) {
document.getElementById("onionr-post-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>"; document.getElementById("onionr-post-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>";
document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon()); document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon());
document.getElementById("onionr-post-creator-user-id").title = currentUser.getID(); document.getElementById("onionr-post-creator-user-id").title = currentUser.getID();
document.getElementById("onionr-post-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>"; document.getElementById("onionr-post-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>";
document.getElementById("onionr-post-focus-reply-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>";
document.getElementById("onionr-post-focus-reply-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>";
} }
viewCurrentProfile = function() { viewCurrentProfile = function() {