Add files
parent
215fbcba68
commit
88df88204c
1
Makefile
1
Makefile
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
sudo pip3 install -r requirements.txt
|
sudo pip3 install -r requirements.txt
|
||||||
|
-@cd onionr/static-data/ui/; ./compile.py
|
||||||
|
|
||||||
install:
|
install:
|
||||||
sudo rm -rf /usr/share/onionr/
|
sudo rm -rf /usr/share/onionr/
|
||||||
|
|
|
@ -129,7 +129,7 @@ class API:
|
||||||
if not hmac.compare_digest(timingToken, self.timeBypassToken):
|
if not hmac.compare_digest(timingToken, self.timeBypassToken):
|
||||||
if elapsed < self._privateDelayTime:
|
if elapsed < self._privateDelayTime:
|
||||||
time.sleep(self._privateDelayTime - elapsed)
|
time.sleep(self._privateDelayTime - elapsed)
|
||||||
return send_from_directory('static-data/ui/', path)
|
return send_from_directory('static-data/ui/dist/', path)
|
||||||
|
|
||||||
@app.route('/client/')
|
@app.route('/client/')
|
||||||
def private_handler():
|
def private_handler():
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
||||||
|
<script src="js/main.js"></script>
|
|
@ -0,0 +1,30 @@
|
||||||
|
<title><$= LANG.ONIONR_TITLE $></title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/main.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/themes/dark.css" />
|
||||||
|
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
||||||
|
<a class="navbar-brand" href="#">Onionr</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="index.html"><$= LANG.TIMELINE $></a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="notifications.html"><$= LANG.NOTIFICATIONS $></a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="messages.html"><$= LANG.MESSAGES $></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
|
@ -0,0 +1,31 @@
|
||||||
|
<!-- POST -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="onionr-post">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2">
|
||||||
|
<img class="onionr-post-user-icon" src="$user-image">
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<a class="onionr-post-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')">$user-name</a>
|
||||||
|
<a class="onionr-post-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-right pl-3 pr-3">
|
||||||
|
<div class="onionr-post-date" data-placement="top" data-toggle="tooltip" title="$date">$date-relative</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onionr-post-content">
|
||||||
|
$content
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onionr-post-controls pt-2">
|
||||||
|
<a href="#!" class="glyphicon glyphicon-heart mr-2">like</a>
|
||||||
|
<a href="#!" class="glyphicon glyphicon-comment mr-2">comment</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST -->
|
|
@ -0,0 +1,123 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import shutil, os, re, json, traceback
|
||||||
|
|
||||||
|
# get user's config
|
||||||
|
settings = {}
|
||||||
|
with open('config.json', 'r') as file:
|
||||||
|
settings = json.loads(file.read())
|
||||||
|
|
||||||
|
# "hardcoded" config, not for user to mess with
|
||||||
|
HEADER_FILE = 'common/header.html'
|
||||||
|
FOOTER_FILE = 'common/footer.html'
|
||||||
|
SRC_DIR = 'src/'
|
||||||
|
DST_DIR = 'dist/'
|
||||||
|
HEADER_STRING = '<header />'
|
||||||
|
FOOTER_STRING = '<footer />'
|
||||||
|
|
||||||
|
# remove dst folder
|
||||||
|
shutil.rmtree(DST_DIR, ignore_errors=True)
|
||||||
|
|
||||||
|
# taken from https://stackoverflow.com/questions/1868714/how-do-i-copy-an-entire-directory-of-files-into-an-existing-directory-using-pyth
|
||||||
|
def copytree(src, dst, symlinks=False, ignore=None):
|
||||||
|
for item in os.listdir(src):
|
||||||
|
s = os.path.join(src, item)
|
||||||
|
d = os.path.join(dst, item)
|
||||||
|
if os.path.isdir(s):
|
||||||
|
shutil.copytree(s, d, symlinks, ignore)
|
||||||
|
else:
|
||||||
|
shutil.copy2(s, d)
|
||||||
|
|
||||||
|
# copy src to dst
|
||||||
|
copytree(SRC_DIR, DST_DIR, False)
|
||||||
|
|
||||||
|
# load in lang map
|
||||||
|
langmap = {}
|
||||||
|
|
||||||
|
with open('lang.json', 'r') as file:
|
||||||
|
langmap = json.loads(file.read())[settings['language']]
|
||||||
|
|
||||||
|
LANG = type('LANG', (), langmap)
|
||||||
|
|
||||||
|
# templating
|
||||||
|
def jsTemplate(template):
|
||||||
|
with open('common/%s.html' % template, 'r') as file:
|
||||||
|
return file.read().replace('\\', '\\\\').replace('\'', '\\\'').replace('\n', "\\\n")
|
||||||
|
|
||||||
|
def htmlTemplate(template):
|
||||||
|
with open('common/%s.html' % template, 'r') as file:
|
||||||
|
return file.read()
|
||||||
|
|
||||||
|
# tag parser
|
||||||
|
def parseTags(contents):
|
||||||
|
# <$ logic $>
|
||||||
|
for match in re.findall(r'(<\$(?!=)(.*?)\$>)', contents):
|
||||||
|
try:
|
||||||
|
out = exec(match[1].strip())
|
||||||
|
contents = contents.replace(match[0], '' if out is None else str(out))
|
||||||
|
except Exception as e:
|
||||||
|
print('Error: Failed to execute python tag (%s): %s\n' % (filename, match[1]))
|
||||||
|
traceback.print_exc()
|
||||||
|
print('\nIgnoring this error, continuing to compile...\n')
|
||||||
|
|
||||||
|
# <$= data $>
|
||||||
|
for match in re.findall(r'(<\$=(.*?)\$>)', contents):
|
||||||
|
try:
|
||||||
|
out = eval(match[1].strip())
|
||||||
|
contents = contents.replace(match[0], '' if out is None else str(out))
|
||||||
|
except NameError as e:
|
||||||
|
name = match[1].strip()
|
||||||
|
print('Warning: %s does not exist, treating as an str' % name)
|
||||||
|
contents = contents.replace(match[0], name)
|
||||||
|
except Exception as e:
|
||||||
|
print('Error: Failed to execute python tag (%s): %s\n' % (filename, match[1]))
|
||||||
|
traceback.print_exc()
|
||||||
|
print('\nIgnoring this error, continuing to compile...\n')
|
||||||
|
|
||||||
|
return contents
|
||||||
|
|
||||||
|
# get header file
|
||||||
|
with open(HEADER_FILE, 'r') as file:
|
||||||
|
HEADER_FILE = file.read()
|
||||||
|
if settings['python_tags']:
|
||||||
|
HEADER_FILE = parseTags(HEADER_FILE)
|
||||||
|
|
||||||
|
# get footer file
|
||||||
|
with open(FOOTER_FILE, 'r') as file:
|
||||||
|
FOOTER_FILE = file.read()
|
||||||
|
if settings['python_tags']:
|
||||||
|
FOOTER_FILE = parseTags(FOOTER_FILE)
|
||||||
|
|
||||||
|
# iterate dst, replace files
|
||||||
|
def iterate(directory):
|
||||||
|
for filename in os.listdir(directory):
|
||||||
|
if filename.split('.')[-1].lower() in ['htm', 'html', 'css', 'js']:
|
||||||
|
try:
|
||||||
|
path = os.path.join(directory, filename)
|
||||||
|
if os.path.isdir(path):
|
||||||
|
iterate(path)
|
||||||
|
else:
|
||||||
|
contents = ''
|
||||||
|
with open(path, 'r') as file:
|
||||||
|
# get file contents
|
||||||
|
contents = file.read()
|
||||||
|
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
with open(path, 'w') as file:
|
||||||
|
# set the header & footer
|
||||||
|
contents = contents.replace(HEADER_STRING, HEADER_FILE)
|
||||||
|
contents = contents.replace(FOOTER_STRING, FOOTER_FILE)
|
||||||
|
|
||||||
|
# do python tags
|
||||||
|
if settings['python_tags']:
|
||||||
|
contents = parseTags(contents)
|
||||||
|
|
||||||
|
# write file
|
||||||
|
file.write(contents)
|
||||||
|
except Exception as e:
|
||||||
|
print('Error: Failed to parse file: %s\n' % filename)
|
||||||
|
traceback.print_exc()
|
||||||
|
print('\nIgnoring this error, continuing to compile...\n')
|
||||||
|
|
||||||
|
iterate(DST_DIR)
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"language" : "eng",
|
||||||
|
"python_tags" : true
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* general formatting */
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.container-small {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.container-large {
|
||||||
|
width: 970px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.container-small {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
.container-large {
|
||||||
|
width: 1170px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.container-small {
|
||||||
|
width: 700px;
|
||||||
|
}
|
||||||
|
.container-large {
|
||||||
|
width: 1500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-small, .container-large {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* navbar */
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin-top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* timeline */
|
||||||
|
|
||||||
|
.onionr-post {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-name {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-id:before { content: "("; }
|
||||||
|
.onionr-post-user-id:after { content: ")"; }
|
||||||
|
|
||||||
|
.onionr-post-content {
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-icon {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* profile */
|
||||||
|
|
||||||
|
.onionr-profile-user-icon {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-profile-username {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
body {
|
||||||
|
background-color: #96928f;
|
||||||
|
color: #25383C;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* timeline */
|
||||||
|
|
||||||
|
.onionr-post {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-name {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-id {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-date {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-content {
|
||||||
|
font-family: sans-serif, serif;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
font-size: 15pt;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1,77 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Onionr UI</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/main.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/themes/dark.css" />
|
||||||
|
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
||||||
|
<a class="navbar-brand" href="#">Onionr</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="index.html">Timeline</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="notifications.html">Notifications</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="messages.html">Messages</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="d-none d-lg-block col-lg-3">
|
||||||
|
<div class="onionr-profile">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<img id="onionr-profile-user-icon" class="onionr-profile-user-icon" src="img/default.png">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<h2 id="onionr-profile-username" class="onionr-profile-username">arinerron</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-lg-6">
|
||||||
|
<div class="row" id="onionr-timeline-posts">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-none d-lg-block col-lg-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="onionr-trending">
|
||||||
|
<h2>Trending</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
|
||||||
|
<script src="js/timeline.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,170 @@
|
||||||
|
/* returns a relative date format, e.g. "5 minutes" */
|
||||||
|
function timeSince(date) {
|
||||||
|
// taken from https://stackoverflow.com/a/3177838/3678023
|
||||||
|
|
||||||
|
var seconds = Math.floor((new Date() - date) / 1000);
|
||||||
|
var interval = Math.floor(seconds / 31536000);
|
||||||
|
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " years";
|
||||||
|
interval = Math.floor(seconds / 2592000);
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " months";
|
||||||
|
interval = Math.floor(seconds / 86400);
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " days";
|
||||||
|
interval = Math.floor(seconds / 3600);
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " hours";
|
||||||
|
interval = Math.floor(seconds / 60);
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " minutes";
|
||||||
|
|
||||||
|
return Math.floor(seconds) + " seconds";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* replace all instances of string */
|
||||||
|
String.prototype.replaceAll = function(search, replacement) {
|
||||||
|
// taken from https://stackoverflow.com/a/17606289/3678023
|
||||||
|
var target = this;
|
||||||
|
return target.split(search).join(replacement);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* sanitizes HTML in a string */
|
||||||
|
function encodeHTML(html) {
|
||||||
|
return String(html).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* URL encodes a string */
|
||||||
|
function encodeURL(url) {
|
||||||
|
return encodeURIComponent(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* user class */
|
||||||
|
class User {
|
||||||
|
constructor() {
|
||||||
|
this.name = 'Unknown';
|
||||||
|
this.id = 'unknown';
|
||||||
|
this.image = 'img/default.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
setName(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
setID(id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
getID() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIcon(image) {
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIcon() {
|
||||||
|
return this.image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* post class */
|
||||||
|
class Post {
|
||||||
|
/* returns the html content of a post */
|
||||||
|
getHTML() {
|
||||||
|
var postTemplate = '<!-- POST -->\
|
||||||
|
<div class="col-12">\
|
||||||
|
<div class="onionr-post">\
|
||||||
|
<div class="row">\
|
||||||
|
<div class="col-2">\
|
||||||
|
<img class="onionr-post-user-icon" src="$user-image">\
|
||||||
|
</div>\
|
||||||
|
<div class="col-10">\
|
||||||
|
<div class="row">\
|
||||||
|
<div class="col">\
|
||||||
|
<a class="onionr-post-user-name" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')">$user-name</a>\
|
||||||
|
<a class="onionr-post-user-id" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>\
|
||||||
|
</div>\
|
||||||
|
<div class="text-right pl-3 pr-3">\
|
||||||
|
<div class="onionr-post-date" data-placement="top" data-toggle="tooltip" title="$date">$date-relative</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
\
|
||||||
|
<div class="onionr-post-content">\
|
||||||
|
$content\
|
||||||
|
</div>\
|
||||||
|
\
|
||||||
|
<div class="onionr-post-controls pt-2">\
|
||||||
|
<a href="#!" class="glyphicon glyphicon-heart mr-2">like</a>\
|
||||||
|
<a href="#!" class="glyphicon glyphicon-comment mr-2">comment</a>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
<!-- END POST -->\
|
||||||
|
';
|
||||||
|
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-name-url', encodeHTML(encodeURL(this.getUser().getName())));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-name', encodeHTML(this.getUser().getName()));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-id-url', encodeHTML(encodeURL(this.getUser().getID())));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-id-truncated', encodeHTML(this.getUser().getID().split('-').slice(0, 4).join('-')));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-id', encodeHTML(this.getUser().getID()));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-image', encodeHTML(this.getUser().getIcon()));
|
||||||
|
postTemplate = postTemplate.replaceAll('$content', encodeHTML(this.getContent()));
|
||||||
|
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate()) + ' ago');
|
||||||
|
postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString());
|
||||||
|
|
||||||
|
return postTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUser(user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUser() {
|
||||||
|
return this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
setContent(content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPostDate(date) { // unix timestamp input
|
||||||
|
if(date instanceof Date)
|
||||||
|
this.date = date;
|
||||||
|
else
|
||||||
|
this.date = new Date(date * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPostDate() {
|
||||||
|
return this.date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handy localstorage functions for quick usage */
|
||||||
|
|
||||||
|
function set(key, val) {
|
||||||
|
return localStorage.setItem(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(key, df) { // df is default
|
||||||
|
value = localStorage.getItem(key);
|
||||||
|
if(value == null)
|
||||||
|
value = df;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(key) {
|
||||||
|
return localStorage.removeItem(key);
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* write a random post to the page, for testing */
|
||||||
|
function addRandomPost() {
|
||||||
|
var post = new Post();
|
||||||
|
var user = new User();
|
||||||
|
var items = ['arinerron', 'beardog108', 'samyk', 'snowden', 'aaronswartz'];
|
||||||
|
user.setName(items[Math.floor(Math.random()*items.length)]);
|
||||||
|
user.setID('i-eat-waffles-often-its-actually-crazy-like-i-dont-know-wow');
|
||||||
|
post.setContent('spammm ' + btoa(Math.random() + ' wow'));
|
||||||
|
post.setUser(user);
|
||||||
|
post.setPostDate(new Date(new Date() - (Math.random() * 1000000)));
|
||||||
|
|
||||||
|
document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i = 0; i < Math.round(50 * Math.random()); i++)
|
||||||
|
addRandomPost();
|
||||||
|
|
||||||
|
function viewProfile(id, name) {
|
||||||
|
document.getElementById("onionr-profile-username").innerHTML = encodeHTML(decodeURIComponent(name));
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"eng" : {
|
||||||
|
"ONIONR_TITLE" : "Onionr UI",
|
||||||
|
|
||||||
|
"TIMELINE" : "Timeline",
|
||||||
|
"NOTIFICATIONS" : "Notifications",
|
||||||
|
"MESSAGES" : "Messages",
|
||||||
|
|
||||||
|
"TRENDING" : "Trending"
|
||||||
|
},
|
||||||
|
|
||||||
|
"spa" : {
|
||||||
|
"ONIONR_TITLE" : "Onionr UI",
|
||||||
|
|
||||||
|
"TIMELINE" : "Linea de Tiempo",
|
||||||
|
"NOTIFICATIONS" : "Notificaciones",
|
||||||
|
"MESSAGES" : "Mensaje",
|
||||||
|
|
||||||
|
"TRENDING" : "Trending"
|
||||||
|
},
|
||||||
|
|
||||||
|
"zho" : {
|
||||||
|
"ONIONR_TITLE" : "洋葱 用户界面",
|
||||||
|
|
||||||
|
"TIMELINE" : "时间线",
|
||||||
|
"NOTIFICATIONS" : "通知",
|
||||||
|
"MESSAGES" : "消息",
|
||||||
|
|
||||||
|
"TRENDING" : "趋势"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* general formatting */
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.container-small {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.container-large {
|
||||||
|
width: 970px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.container-small {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
.container-large {
|
||||||
|
width: 1170px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.container-small {
|
||||||
|
width: 700px;
|
||||||
|
}
|
||||||
|
.container-large {
|
||||||
|
width: 1500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-small, .container-large {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* navbar */
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin-top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* timeline */
|
||||||
|
|
||||||
|
.onionr-post {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-name {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-id:before { content: "("; }
|
||||||
|
.onionr-post-user-id:after { content: ")"; }
|
||||||
|
|
||||||
|
.onionr-post-content {
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-icon {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* profile */
|
||||||
|
|
||||||
|
.onionr-profile-user-icon {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-profile-username {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
body {
|
||||||
|
background-color: #96928f;
|
||||||
|
color: #25383C;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* timeline */
|
||||||
|
|
||||||
|
.onionr-post {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-name {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-user-id {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-date {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-content {
|
||||||
|
font-family: sans-serif, serif;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
font-size: 15pt;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1,43 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<header />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="d-none d-lg-block col-lg-3">
|
||||||
|
<div class="onionr-profile">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<img id="onionr-profile-user-icon" class="onionr-profile-user-icon" src="img/default.png">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<h2 id="onionr-profile-username" class="onionr-profile-username">arinerron</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12 col-lg-6">
|
||||||
|
<div class="row" id="onionr-timeline-posts">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-none d-lg-block col-lg-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="onionr-trending">
|
||||||
|
<h2><$= LANG.TRENDING $></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer />
|
||||||
|
<script src="js/timeline.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,167 @@
|
||||||
|
|
||||||
|
/* handy localstorage functions for quick usage */
|
||||||
|
|
||||||
|
function set(key, val) {
|
||||||
|
return localStorage.setItem(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(key, df) { // df is default
|
||||||
|
value = localStorage.getItem(key);
|
||||||
|
if(value == null)
|
||||||
|
value = df;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(key) {
|
||||||
|
return localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
var usermap = JSON.parse(get('usermap', '{}'));
|
||||||
|
|
||||||
|
function getUserMap() {
|
||||||
|
return usermap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserializeUser(id) {
|
||||||
|
var serialized = getUserMap()[id]
|
||||||
|
var user = new User();
|
||||||
|
user.setName(serialized['name']);
|
||||||
|
user.setID(serialized['id']);
|
||||||
|
user.setIcon(serialized['icon']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returns a relative date format, e.g. "5 minutes" */
|
||||||
|
function timeSince(date) {
|
||||||
|
// taken from https://stackoverflow.com/a/3177838/3678023
|
||||||
|
|
||||||
|
var seconds = Math.floor((new Date() - date) / 1000);
|
||||||
|
var interval = Math.floor(seconds / 31536000);
|
||||||
|
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " years";
|
||||||
|
interval = Math.floor(seconds / 2592000);
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " months";
|
||||||
|
interval = Math.floor(seconds / 86400);
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " days";
|
||||||
|
interval = Math.floor(seconds / 3600);
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " hours";
|
||||||
|
interval = Math.floor(seconds / 60);
|
||||||
|
if (interval > 1)
|
||||||
|
return interval + " minutes";
|
||||||
|
|
||||||
|
return Math.floor(seconds) + " seconds";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* replace all instances of string */
|
||||||
|
String.prototype.replaceAll = function(search, replacement) {
|
||||||
|
// taken from https://stackoverflow.com/a/17606289/3678023
|
||||||
|
var target = this;
|
||||||
|
return target.split(search).join(replacement);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* sanitizes HTML in a string */
|
||||||
|
function encodeHTML(html) {
|
||||||
|
return String(html).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* URL encodes a string */
|
||||||
|
function encodeURL(url) {
|
||||||
|
return encodeURIComponent(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* user class */
|
||||||
|
class User {
|
||||||
|
constructor() {
|
||||||
|
this.name = 'Unknown';
|
||||||
|
this.id = 'unknown';
|
||||||
|
this.image = 'img/default.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
setName(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
setID(id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
getID() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIcon(image) {
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIcon() {
|
||||||
|
return this.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return {
|
||||||
|
'name' : this.getName(),
|
||||||
|
'id' : this.getID(),
|
||||||
|
'icon' : this.getIcon()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
remember() {
|
||||||
|
usermap[this.getID()] = this.serialize();
|
||||||
|
set('usermap', JSON.stringify(usermap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* post class */
|
||||||
|
class Post {
|
||||||
|
/* returns the html content of a post */
|
||||||
|
getHTML() {
|
||||||
|
var postTemplate = '<$= jsTemplate('onionr-timeline-post') $>';
|
||||||
|
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-name-url', encodeHTML(encodeURL(this.getUser().getName())));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-name', encodeHTML(this.getUser().getName()));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-id-url', encodeHTML(encodeURL(this.getUser().getID())));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-id-truncated', encodeHTML(this.getUser().getID().split('-').slice(0, 4).join('-')));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-id', encodeHTML(this.getUser().getID()));
|
||||||
|
postTemplate = postTemplate.replaceAll('$user-image', encodeHTML(this.getUser().getIcon()));
|
||||||
|
postTemplate = postTemplate.replaceAll('$content', encodeHTML(this.getContent()));
|
||||||
|
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate()) + ' ago');
|
||||||
|
postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString());
|
||||||
|
|
||||||
|
return postTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUser(user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUser() {
|
||||||
|
return this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
setContent(content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPostDate(date) { // unix timestamp input
|
||||||
|
if(date instanceof Date)
|
||||||
|
this.date = date;
|
||||||
|
else
|
||||||
|
this.date = new Date(date * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPostDate() {
|
||||||
|
return this.date;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* write a random post to the page, for testing */
|
||||||
|
function addRandomPost() {
|
||||||
|
var post = new Post();
|
||||||
|
var user = new User();
|
||||||
|
var items = ['arinerron', 'beardog108', 'samyk', 'snowden', 'aaronswartz'];
|
||||||
|
user.setName(items[Math.floor(Math.random()*items.length)]);
|
||||||
|
user.setID('i-eat-waffles-often-its-actually-crazy-like-i-dont-know-wow');
|
||||||
|
post.setContent('spammm ' + btoa(Math.random() + ' wow'));
|
||||||
|
post.setUser(user);
|
||||||
|
post.setPostDate(new Date(new Date() - (Math.random() * 1000000)));
|
||||||
|
|
||||||
|
document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i = 0; i < Math.round(50 * Math.random()); i++)
|
||||||
|
addRandomPost();
|
||||||
|
|
||||||
|
function viewProfile(id, name) {
|
||||||
|
document.getElementById("onionr-profile-username").innerHTML = encodeHTML(decodeURIComponent(name));
|
||||||
|
}
|
Loading…
Reference in New Issue