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:
parent
de03ca02f6
commit
9118dfaba7
39
css/base.css
39
css/base.css
|
@ -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
44
js/postboxes.js
Normal 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
172
server.py
|
@ -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):
|
||||
|
|
|
@ -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>
|
|
@ -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
|
|
@ -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'"><- 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>
|
Loading…
Reference in New Issue
Block a user