2
2
mirror of https://github.com/bbj-dev/bbj synced 2024-06-13 04:26:41 +00:00

web interface now has posting

This commit is contained in:
r 2024-04-22 14:07:13 -05:00
parent de03ca02f6
commit 9118dfaba7
6 changed files with 386 additions and 149 deletions

View File

@ -1,6 +1,7 @@
body {
font-family: monospace;
font-size: 125%;
padding-top: 1em;
}
.thread {
@ -10,6 +11,14 @@ body {
max-width: 40em;
}
.pinned {
background-color: aquamarine;
}
.bookmarked {
background-color: lightskyblue;
}
.message {
padding: 1em;
margin: 1em;
@ -17,6 +26,36 @@ body {
max-width: 60em;
}
#navbar {
/* overflow: hidden; */
background-color: #CCC;
font-size: 1em;
padding: 0.25em;
margin: 0px;
position: fixed;
top: 0;
right: 0;
width: 100%;
}
.bbjLogo {
background-color: black;
color: white;
padding: 0.25em
}
.newThreadButton {
font-size: 140%;
padding: 0.5em;
margin: 1em;
margin-top: 1.5em;
background-color: black;
color: white;
text-decoration: none;
display: inline-block;
}
pre {
white-space: pre-wrap;
}

44
js/postboxes.js Normal file
View File

@ -0,0 +1,44 @@
function revealPostReplyBox(post_id) {
domElement = document.getElementById("replyBox" + post_id)
document.getElementById("replyLink" + post_id).remove()
form = document.createElement("form")
form.setAttribute("method", "post")
form.setAttribute("action", "/threadReply")
textarea = document.createElement("textarea")
textarea.setAttribute("class", "directReplyBox")
textarea.setAttribute("id", "postBody")
textarea.setAttribute("name", "postBody")
textarea.setAttribute("rows", "10")
textarea.setAttribute("cols", "50")
textarea.value = ">>" + post_id + " \n"
input = document.createElement("input")
input.setAttribute("name", "threadId")
input.value = thread_id
input.style = "display:none"
submit = document.createElement("input")
submit.setAttribute("class", "directReplyBox")
submit.setAttribute("type", "submit")
submit.setAttribute("value", "Submit")
form.appendChild(textarea)
form.appendChild(input)
form.appendChild(document.createElement("br"))
form.appendChild(submit)
domElement.appendChild(form)
}
function revealThreadCreateForm() {
form = document.getElementById("threadCreateBox")
if (form.style.display == "none") {
form.style.display = "block"
} else {
form.style.display = "none"
}
}

172
server.py
View File

@ -2,6 +2,7 @@ from src.exceptions import BBJException, BBJParameterError, BBJUserError
from jinja2 import Environment, FileSystemLoader, select_autoescape
from src import db, schema, formatting
from os.path import abspath
from hashlib import sha256
from functools import wraps
from uuid import uuid1
from sys import argv
@ -674,51 +675,182 @@ class API(object):
"response instead of a special object.")
)
testing = None
class HTML(object):
"""
This object contains all of the endpoints for the HTML application.
Javascript isn't used, and a more "retro" aesthetic is aimed for.
This is not a full javascript fronted developed with a framework like React.
This fits in line with the general "theme" of the tilde servers
this application was originally designed for. If you want a more
modern implementation, a javascript-based client can be developed.
modern implementation, a fully javascript-based client can be developed.
But the server will not have one built in.
"""
@cherrypy.expose
def index(self):
def login(self, username=None, password=None):
database = sqlite3.connect(dbname)
cookie = cherrypy.request.cookie
include_op = "include_op" in cookie.keys() and cookie["include_op"]
threads = db.thread_index(database, include_op=include_op)
usermap = create_usermap(database, threads, True)
template = template_environment.get_template("threadIndex.html")
if username and password:
user = db.user_resolve(database, username)
auth_hash = sha256(bytes(password, "utf8")).hexdigest()
if not user:
return "User not registered"
elif auth_hash.lower() != user["auth_hash"].lower():
return "Authorization info incorrect."
cherrypy.response.cookie["username"] = username
cherrypy.response.cookie["username"]["max-age"] = 34560000
cherrypy.response.cookie["auth_hash"] = auth_hash
cherrypy.response.cookie["auth_hash"]["max-age"] = 34560000
raise cherrypy.HTTPRedirect("/index")
if "username" in cookie and "auth_hash" in cookie:
user = db.user_resolve(database, cookie["username"].value)
if cookie["auth_hash"].value.lower() == user["auth_hash"]:
authorized_user = user
else:
authorized_user = None
else:
authorized_user = None
template = template_environment.get_template("login.html")
variables = {
"threads": threads,
"include_op": include_op,
"usermap": usermap
"authorized_user": authorized_user
}
return template.render(variables)
@cherrypy.expose
def thread(self, id=None):
if not id:
return "None"
def logout(self):
cookie_in = cherrypy.request.cookie
if "username" in cookie_in and "auth_hash" in cookie_in:
cherrypy.response.cookie["username"] = ""
cherrypy.response.cookie["username"]["expires"] = 0
cherrypy.response.cookie["auth_hash"] = ""
cherrypy.response.cookie["auth_hash"]["expires"] = 0
raise cherrypy.HTTPRedirect("/index")
@cherrypy.expose
def setBookmark(self, bookmarkId=None, delBookmark=None):
if "bookmarks" in cherrypy.request.cookie:
bookmarks = json.loads(cherrypy.request.cookie["bookmarks"].value)
else:
bookmarks = []
database = sqlite3.connect(dbname)
thread = db.thread_get(database, id)
usermap = create_usermap(database, thread["messages"])
template = template_environment.get_template("threadLoad.html")
threads = db.thread_index(database)
if bookmarkId:
if bookmarkId in [thread["thread_id"] for thread in threads]:
bookmarks.append(bookmarkId)
elif delBookmark:
if delBookmark in bookmarks:
bookmarks.remove(delBookmark)
cherrypy.response.cookie["bookmarks"] = json.dumps(bookmarks)
cherrypy.response.cookie["bookmarks"]["max-age"] = 34560000
raise cherrypy.HTTPRedirect("/index")
@cherrypy.expose
def index(self, bookmarkId=None, delBookmark=None):
database = sqlite3.connect(dbname)
cookie = cherrypy.request.cookie
include_op = "include_op" in cookie and cookie["include_op"]
threads = db.thread_index(database, include_op=include_op)
usermap = create_usermap(database, threads, True)
if "username" in cookie and "auth_hash" in cookie:
user = db.user_resolve(database, cookie["username"].value)
if cookie["auth_hash"].value.lower() == user["auth_hash"]:
authorized_user = user
else:
authorized_user = None
else:
authorized_user = None
pinned_threads = [thread for thread in threads if thread["pinned"]]
if "bookmarks" in cookie:
loads = json.loads(cookie["bookmarks"].value)
bookmarked_threads = [thread for thread in threads if thread["thread_id"] in loads]
threads = [
thread for thread in threads
if not thread["pinned"]
and not thread["thread_id"] in loads
]
else:
bookmarked_threads = []
threads = [
thread for thread in threads
if not thread["pinned"]
]
template = template_environment.get_template("threadIndex.html")
variables = {
"thread": thread,
"usermap": usermap
"pinned_threads": pinned_threads,
"bookmarked_threads": bookmarked_threads,
"threads": threads,
"include_op": include_op,
"usermap": usermap,
"authorized_user": authorized_user
}
return template.render(variables)
@cherrypy.expose
def thread(self, id=None):
if not id:
return "Please supply a Thread ID"
database = sqlite3.connect(dbname)
cookie = cherrypy.request.cookie
thread = db.thread_get(database, id)
usermap = create_usermap(database, thread["messages"])
if "username" in cookie and "auth_hash" in cookie:
user = db.user_resolve(database, cookie["username"].value)
if cookie["auth_hash"].value.lower() == user["auth_hash"]:
authorized_user = user
else:
authorized_user = None
else:
authorized_user = None
template = template_environment.get_template("threadLoad.html")
variables = {
"thread": thread,
"usermap": usermap,
"authorized_user": authorized_user
}
return template.render(variables)
@cherrypy.expose
def threadSubmit(self, title=None, postContent=None):
database = sqlite3.connect(dbname)
cookie = cherrypy.request.cookie
if "username" in cookie and "auth_hash" in cookie:
user = db.user_resolve(database, cookie["username"].value)
if cookie["auth_hash"].value.lower() == user["auth_hash"]:
if title and postContent and title.strip() and postContent.strip():
thread = db.thread_create(database, user["user_id"], postContent, title)
raise cherrypy.HTTPRedirect("/thread?id=" + thread["thread_id"])
else:
return "Post or Title are empty"
else:
return "Not logged in."
@cherrypy.expose
def threadReply(self, postBody, threadId):
database = sqlite3.connect(dbname)
cookie = cherrypy.request.cookie
if "username" in cookie and "auth_hash" in cookie:
user = db.user_resolve(database, cookie["username"].value)
if cookie["auth_hash"].value.lower() != user["auth_hash"]:
return "Authorization info not correct."
db.thread_reply(database, user["user_id"], threadId, postBody)
raise cherrypy.HTTPRedirect("/thread?id=" + threadId)
return "User not logged in"
def test(self):
return "Hello world!"
def api_http_error(status, message, traceback, version):

View File

@ -2,22 +2,21 @@
<html>
<head>
<title>Log In: BBJ</title>
<link rel="stylesheet" href="/css/login.css">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/base.css">
</head>
<body>
<center>
<form action="/thread" autocomplete="off">
<label for="coords">Coordinates:</label>
<br>
<input type="text" id="coords" name="coords">
<br>
<label for="label">Label:</label>
<br>
<input type="text" id="label" name="label">
<br>
<input type="submit" value="Request Document">
</form>
<center>
{% if authorized_user %}
<span>Currently logged in as <span class="color{{ authorized_user['color'] }}">~{{ authorized_user["user_name"] }}</span></span>
<br>
<a href="/logout">Log out.</a>
{% endif %}
<form class="loginForm" method="post" action="/login">
<label for="username">Username:</label>
<input type="text" name="username" /><br />
<label for="password">Password:</label>
<input type="password" name="password" /><br />
<input type="submit" value="Log in" />
</form>
</body>
</html>

View File

@ -1,61 +1,98 @@
<!-- {
'thread_id': '41556750260c11e78aa402e6a9e126c8',
'author': 'd2ab28b4215911e78aa402e6a9e126c8',
'title': 'New here? a thread for introductions',
'last_mod': 1713633906.6122937,
'created': 1492721973.5667894,
'reply_count': 277,
'pinned': False,
'last_author': '567c3dd0ff3911eea983a1cead9c88ba',
'messages': [
{
'thread_id': '41556750260c11e78aa402e6a9e126c8',
'post_id': 0,
'author': 'd2ab28b4215911e78aa402e6a9e126c8',
'created': 1492721973.5667894,
'edited': False,
'body': "Message body",
'send_raw': False
}
]
} -->
<!-- '841905e68d1011eb9227cd49f80bac2d':
{
'user_id': '841905e68d1011eb9227cd49f80bac2d',
'user_name': 'joy',
'quip': '',
'bio': '',
'color': 0,
'is_admin': False,
'created': 1616638764.0158434
} -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/base.css">
<title>Thread Index: BBJ</title>
</head>
<body>
{% for thread in threads %}
<div class="thread">
<a href="thread?id={{ thread['thread_id'] }}">{{ thread["title"] }}</a>
<br>
by <span class="color{{ usermap[thread['author']]['color'] }}">{{ usermap[thread["author"]]["user_name"] }}</span>
<br>
Created: <span class="datetime">{{ thread["created"] }}</span>
<br>
{{ thread["reply_count"] }} replies; active <span class="datetime">{{ thread["last_mod"] }}</span>
<br>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/base.css">
<title>Thread Index: BBJ</title>
</head>
<body>
<div id="navbar">
<span class="bbjLogo">BBJ</span>
{% if authorized_user %}
<span>Logged in as <span class="color{{ authorized_user['color']}}">~{{ authorized_user["user_name"] }}</span></span>
{% else %}
<span>Not logged in.</span>
{% endif %}
<a class="navbarLink" href="/login">Set login info.</a>
</div>
{% if authorized_user %}
<a class="newThreadButton" href="javascript:revealThreadCreateForm()">Create New Thread</a>
<div class="threadCreateBox" id="threadCreateBox" style="display: none;">
<form method="post" action="/threadSubmit">
<label for="title">Title:</label><br>
<input type="text" id="title" name="title"><br>
<label for="postContent">Content:</label><br>
<textarea type="text" id="postContent" name="postContent" rows="10" cols="50"></textarea><br>
<input type="submit" value="Post">
</form>
</div>
{% endfor %}
</body>
<script src="/js/datetime.js"></script>
</html>
{% endif %}
{% if pinned_threads %}
<div class="pinnedContainer">
<h2>Pinned Threads</h2>
{% for thread in pinned_threads %}
<div class="thread pinned">
<a href="thread?id={{ thread['thread_id'] }}">{{ thread["title"] }}</a>
<br>
by <span class="color{{ usermap[thread['author']]['color'] }}">{{ usermap[thread["author"]]["user_name"] }}</span>
<br>
Created: <span class="datetime">{{ thread["created"] }}</span>
<br>
{{ thread["reply_count"] }} replies; active <span class="datetime">{{ thread["last_mod"] }}</span>
<br>
</div>
{% endfor %}
</div>
{% endif %}
{% if bookmarked_threads %}
<div class="bookmarksContainer">
<h2>Bookmarked Threads</h2>
{% for thread in bookmarked_threads %}
<div class="thread bookmarked">
<a href="thread?id={{ thread['thread_id'] }}">{{ thread["title"] }}</a>
<br>
by <span class="color{{ usermap[thread['author']]['color'] }}">{{ usermap[thread["author"]]["user_name"] }}</span>
<br>
Created: <span class="datetime">{{ thread["created"] }}</span>
<br>
{{ thread["reply_count"] }} replies; active <span class="datetime">{{ thread["last_mod"] }}</span>
<br>
<a href="setBookmark?delBookmark={{ thread['thread_id'] }}">Unbookmark this thread.</a>
</div>
{% endfor %}
</div>
{% endif %}
{% if threads %}
<div class="threadContainer">
<h2>Threads</h2>
{% for thread in threads %}
<div class="thread">
<a href="thread?id={{ thread['thread_id'] }}">{{ thread["title"] }}</a>
<br>
by <span class="color{{ usermap[thread['author']]['color'] }}">{{ usermap[thread["author"]]["user_name"] }}</span>
<br>
Created: <span class="datetime">{{ thread["created"] }}</span>
<br>
{{ thread["reply_count"] }} replies; active <span class="datetime">{{ thread["last_mod"] }}</span>
<br>
<a href="setBookmark?bookmarkId={{ thread['thread_id'] }}">Bookmark this thread.</a>
</div>
{% endfor %}
</div>
{% endif %}
{% if not threads %}
<h2>There are no threads!</h2>
{% endif %}
</body>
<script src="/js/datetime.js"></script>
<script src="/js/postboxes.js"></script>
</html>replies

View File

@ -1,59 +1,45 @@
<!-- {
"thread_id": "55ed6cc6ec7511ee9da5d7b798671d27",
"author": "e7694b188ccd11eab5efc91820ae1c8d",
"title": "joyful things",
"last_mod": 1713570345.1295059,
"created": 1711569919.20138,
"reply_count": 3,threads
"pinned": false,
"last_author": "9e598ed21f8a11e78aa402e6a9e126c8",
"messages": [
{
"thread_id": "55ed6cc6ec7511ee9da5d7b798671d27",
"post_id": 0,
"author": "e7694b188ccd11eab5efc91820ae1c8d",
"created": 1711569919.20138,
"edited": false,
"body": "Share something that has brought you joy today. :) And if you're feeling so inspired, maybe take a look at the other joyful things people have posted and ask a question or share how their post makes you feel.\n\nMy hot cup of coffee against the backdrop of a gray, rainy day brought me much joy this morning.",
"send_raw": false
},
{
"thread_id": "55ed6cc6ec7511ee9da5d7b798671d27",
"post_id": 1,
"author": "cf6d84ecb8f511ec827f01ac1bddac88",
"created": 1711585515.4367874,
"edited": false,
"body": "I live in a society where I can just get fresh fruit whenever I want to. I ate\nan orange with lunch today and it was SO good. I am very thankful.",
"send_raw": false
}
]
}
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/base.css">
<title>{{ thread["title"] }}: BBJ</title>
</head>
<script>
thread_id = '{{ thread["thread_id"] }}'
</script>
<body>
<div id="navbar">
<span class="bbjLogo" onclick="window.location.href='/index'">&lt;- Index</span>
{% if authorized_user %}
<span>Logged in as <span class="color{{ authorized_user['color']}}">~{{ authorized_user["user_name"] }}</span></span>
{% else %}
<span>Not logged in.</span>
{% endif %}
<a class="navbarLink" href="/login">Set login info.</a>
</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/base.css">
<title>{{ thread["title"] }}: BBJ</title>
</head>
<body>
{% for message in thread["messages"] %}
<div class="message">
<div class="messageHeader">
>{{ message["post_id"] }}
<span class="color{{ usermap[message['author']]['color'] }}">{{ usermap[message["author"]]["user_name"] }}</span>
@ <span class="datetime">{{ message["created"] }}</span>
</div>
<div class="messageBody">
<pre>{{ message["body"] }}</pre>
</div>
{% for message in thread["messages"] %}
<div class="message">
<div class="messageHeader">
>{{ message["post_id"] }}
<span class="color{{ usermap[message['author']]['color'] }}">{{ usermap[message["author"]]["user_name"] }}</span>
@ <span class="datetime">{{ message["created"] }}</span>
</div>
{% endfor %}
</body>
<script src="/js/datetime.js"></script>
</html>
<div class="messageBody">
<pre>{{ message["body"] }}</pre>
</div>
<div class="messageFooter">
{% if authorized_user %}
<a id="replyLink{{ message['post_id'] }}" href="javascript:revealPostReplyBox({{ message['post_id'] }})">Direct reply.</a>
<div class="directReplyBoxContainer" id="replyBox{{ message['post_id'] }}"></div>
{% endif %}
</div>
</div>
{% endfor %}
</body>
<script src="/js/datetime.js"></script>
<script src="/js/postboxes.js"></script>
</html>