diff --git a/Makefile b/Makefile
index 472ffc2d..c51fc72b 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,7 @@
setup:
sudo pip3 install -r requirements.txt
+ -@cd onionr/static-data/ui/; ./compile.py
install:
sudo rm -rf /usr/share/onionr/
diff --git a/onionr/api.py b/onionr/api.py
index 256f55f9..705a4781 100755
--- a/onionr/api.py
+++ b/onionr/api.py
@@ -129,7 +129,7 @@ class API:
if not hmac.compare_digest(timingToken, self.timeBypassToken):
if elapsed < self._privateDelayTime:
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/')
def private_handler():
diff --git a/onionr/static-data/ui/common/footer.html b/onionr/static-data/ui/common/footer.html
new file mode 100644
index 00000000..6b5cfb06
--- /dev/null
+++ b/onionr/static-data/ui/common/footer.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/onionr/static-data/ui/common/header.html b/onionr/static-data/ui/common/header.html
new file mode 100644
index 00000000..2a2b4f56
--- /dev/null
+++ b/onionr/static-data/ui/common/header.html
@@ -0,0 +1,30 @@
+
<$= LANG.ONIONR_TITLE $>
+
+
+
+
+
+
+
+
+
+
diff --git a/onionr/static-data/ui/common/onionr-timeline-post.html b/onionr/static-data/ui/common/onionr-timeline-post.html
new file mode 100644
index 00000000..ceff5c65
--- /dev/null
+++ b/onionr/static-data/ui/common/onionr-timeline-post.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/onionr/static-data/ui/compile.py b/onionr/static-data/ui/compile.py
new file mode 100755
index 00000000..c93e4aa7
--- /dev/null
+++ b/onionr/static-data/ui/compile.py
@@ -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 = ''
+FOOTER_STRING = ''
+
+# 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)
diff --git a/onionr/static-data/ui/config.json b/onionr/static-data/ui/config.json
new file mode 100644
index 00000000..f87539ac
--- /dev/null
+++ b/onionr/static-data/ui/config.json
@@ -0,0 +1,4 @@
+{
+ "language" : "eng",
+ "python_tags" : true
+}
diff --git a/onionr/static-data/ui/dist/css/main.css b/onionr/static-data/ui/dist/css/main.css
new file mode 100644
index 00000000..d9e76be6
--- /dev/null
+++ b/onionr/static-data/ui/dist/css/main.css
@@ -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;
+}
+
diff --git a/onionr/static-data/ui/dist/css/themes/dark.css b/onionr/static-data/ui/dist/css/themes/dark.css
new file mode 100644
index 00000000..4a547a29
--- /dev/null
+++ b/onionr/static-data/ui/dist/css/themes/dark.css
@@ -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;
+}
diff --git a/onionr/static-data/ui/dist/img/default.png b/onionr/static-data/ui/dist/img/default.png
new file mode 100644
index 00000000..c4227916
Binary files /dev/null and b/onionr/static-data/ui/dist/img/default.png differ
diff --git a/onionr/static-data/ui/dist/index.html b/onionr/static-data/ui/dist/index.html
new file mode 100644
index 00000000..886404dc
--- /dev/null
+++ b/onionr/static-data/ui/dist/index.html
@@ -0,0 +1,77 @@
+
+
+
+ Onionr UI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
arinerron
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/onionr/static-data/ui/dist/js/main.js b/onionr/static-data/ui/dist/js/main.js
new file mode 100644
index 00000000..0141c5a1
--- /dev/null
+++ b/onionr/static-data/ui/dist/js/main.js
@@ -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, '"');
+}
+
+/* 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 = '\
+\
+
\
+
\
+
\
+
\
+
\
+
\
+
\
+ \
+
\
+ $content\
+
\
+ \
+
\
+
\
+
\
+
\
+
\
+\
+';
+
+ 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);
+}
diff --git a/onionr/static-data/ui/dist/js/timeline.js b/onionr/static-data/ui/dist/js/timeline.js
new file mode 100644
index 00000000..834568ca
--- /dev/null
+++ b/onionr/static-data/ui/dist/js/timeline.js
@@ -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));
+}
diff --git a/onionr/static-data/ui/lang.json b/onionr/static-data/ui/lang.json
new file mode 100644
index 00000000..49bde688
--- /dev/null
+++ b/onionr/static-data/ui/lang.json
@@ -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" : "趋势"
+ }
+}
diff --git a/onionr/static-data/ui/src/css/main.css b/onionr/static-data/ui/src/css/main.css
new file mode 100644
index 00000000..d9e76be6
--- /dev/null
+++ b/onionr/static-data/ui/src/css/main.css
@@ -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;
+}
+
diff --git a/onionr/static-data/ui/src/css/themes/dark.css b/onionr/static-data/ui/src/css/themes/dark.css
new file mode 100644
index 00000000..4a547a29
--- /dev/null
+++ b/onionr/static-data/ui/src/css/themes/dark.css
@@ -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;
+}
diff --git a/onionr/static-data/ui/src/img/default.png b/onionr/static-data/ui/src/img/default.png
new file mode 100644
index 00000000..c4227916
Binary files /dev/null and b/onionr/static-data/ui/src/img/default.png differ
diff --git a/onionr/static-data/ui/src/index.html b/onionr/static-data/ui/src/index.html
new file mode 100644
index 00000000..b23945b8
--- /dev/null
+++ b/onionr/static-data/ui/src/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
arinerron
+
+
+
+
+
+
+
+
+
+
+
+
<$= LANG.TRENDING $>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/onionr/static-data/ui/src/js/main.js b/onionr/static-data/ui/src/js/main.js
new file mode 100644
index 00000000..7208cd21
--- /dev/null
+++ b/onionr/static-data/ui/src/js/main.js
@@ -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, '"');
+}
+
+/* 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;
+ }
+}
diff --git a/onionr/static-data/ui/src/js/timeline.js b/onionr/static-data/ui/src/js/timeline.js
new file mode 100644
index 00000000..834568ca
--- /dev/null
+++ b/onionr/static-data/ui/src/js/timeline.js
@@ -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));
+}