message list
This commit is contained in:
parent
103998c0e6
commit
2b34f47754
12 changed files with 222 additions and 9 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"TAUTH",
|
||||
"tcommon",
|
||||
"tmessage"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ SECRET_KEY = config["services"]["message"]["token"]
|
|||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
STATIC_DIR = BASE_DIR / "tmessage/static"
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
INSTALLED_APPS = [
|
||||
|
@ -74,6 +75,4 @@ LANGUAGE_CODE = "en-us"
|
|||
TIME_ZONE = "UTC"
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
STATIC_URL = "/static/"
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
STATIC_ROOT = BASE_DIR / "collected-static"
|
||||
|
|
17
tmessage/static/css/messages.css
Normal file
17
tmessage/static/css/messages.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.message-container {
|
||||
width: calc(90vw - 40px);
|
||||
max-width: 400px;
|
||||
padding: 20px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 6px;
|
||||
outline: 1px solid rgb(var(--accent));
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.message-container textarea {
|
||||
width: calc(90vw - 52px);
|
||||
max-width: 388px;
|
||||
resize: vertical;
|
||||
height: 100px;
|
||||
}
|
80
tmessage/static/js/messages.js
Normal file
80
tmessage/static/js/messages.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
let offset;
|
||||
let timelineElement = document.getElementById("messages");
|
||||
|
||||
function escapeHTML(content) {
|
||||
return content.replaceAll("&", "&").replaceAll("<", ">").replaceAll(">", "<");
|
||||
}
|
||||
|
||||
function getMessageHTML(messageJSON, canRespond) {
|
||||
return `<div class="message-container" data-message-id="${messageJSON.id}">
|
||||
<blockquote class="message">
|
||||
<div><b>${messageJSON.from || "Anonymous"}</b> writes:</div>
|
||||
<pre class="not-code">${escapeHTML(messageJSON.content)}</pre>
|
||||
</blockquote>
|
||||
${messageJSON.response ? `<pre class="not-code">${escapeHTML(messageJSON.response)}</pre>` : (
|
||||
canRespond ? `
|
||||
<div><textarea placeholder="Your response..." maxlength="10000"></textarea></div>
|
||||
<div><button onclick="replyTo(${messageJSON.id})">Reply</button></div>
|
||||
` : `<i>No response</i>`
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function _updateURL_replaceElement(element, to) {
|
||||
let newElement = document.createElement(to);
|
||||
newElement.dataset.url = element.dataset.url;
|
||||
newElement.dataset.id = element.dataset.id;
|
||||
newElement.innerHTML = element.innerHTML;
|
||||
newElement.setAttribute("href", element.getAttribute("href"));
|
||||
|
||||
if (to == "a") {
|
||||
newElement.classList.add("not-bold");
|
||||
}
|
||||
|
||||
element.replaceWith(newElement);
|
||||
}
|
||||
|
||||
function updateURL(switchID) {
|
||||
let switchElement = document.getElementById("switch").querySelector(`[data-id="${switchID}"]`);
|
||||
|
||||
for (const el of document.getElementById("switch").querySelectorAll("b")) {
|
||||
_updateURL_replaceElement(el, "a");
|
||||
}
|
||||
|
||||
_updateURL_replaceElement(switchElement, "b");
|
||||
url = switchElement.dataset.url;
|
||||
fetchMessages(true);
|
||||
}
|
||||
|
||||
function fetchMessages(fetchFromStart=false) {
|
||||
document.getElementById("refresh").setAttribute("disabled", "");
|
||||
timelineElement.innerHTML = "<i>Loading...</i>"
|
||||
|
||||
fetch(url + (fetchFromStart === true ? "" : `${url.includes("?") ? "&" : "?"}offset=${offset}`))
|
||||
.then((response) => (response.json()))
|
||||
.then((json) => {
|
||||
if (json.success) {
|
||||
out = "";
|
||||
|
||||
if (json.messages.length == 0) {
|
||||
out = "<i>No messages</i>";
|
||||
} else {
|
||||
for (const message of json.messages) {
|
||||
out += getMessageHTML(message, json.canRespond);
|
||||
offset = message.id;
|
||||
}
|
||||
}
|
||||
|
||||
timelineElement.innerHTML = out;
|
||||
} else {
|
||||
timelineElement.innerText = "Something went wrong!";
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
timelineElement.innerText = `Couldn't fetch timeline: ${err}`;
|
||||
document.getElementById("refresh").removeAttribute("disabled");
|
||||
});
|
||||
}
|
||||
|
||||
fetchMessages(true);
|
||||
document.getElementById("refresh").addEventListener("click", (() => { fetchMessages(true); }));
|
|
@ -6,7 +6,7 @@
|
|||
<hr>
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<p><textarea name="message" id="message" required maxlength="10000" placeholder="Your message"></textarea></p>
|
||||
<p><textarea class="auto-size" name="message" id="message" required maxlength="10000" placeholder="Your message"></textarea></p>
|
||||
{% if self_username %}
|
||||
Logged in as <b>{{ self_username }}</b>
|
||||
<div>
|
||||
|
|
19
tmessage/templates/messages.html
Normal file
19
tmessage/templates/messages.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="/static/css/messages.css">
|
||||
<script>let url = "/api/messages/";</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Your messages</h1>
|
||||
Logged in as {{ username }}
|
||||
<hr>
|
||||
<button id="refresh">Refresh</button>
|
||||
<p id="switch">
|
||||
<b data-url="/api/messages/" data-id="all" href="javascript:(() => { updateURL('all'); })()">All messages</b> -
|
||||
<a data-url="/api/messages/?unread" data-id="unread" class="not-bold" href="javascript:(() => { updateURL('unread'); })()">Not responded</a>
|
||||
</p>
|
||||
<div id="messages"><i>Loading...</i></div>
|
||||
<script src="/static/js/messages.js?v={{ config.version_str }}"></script>
|
||||
{% endblock %}
|
|
@ -1,12 +1,15 @@
|
|||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from .views import auth, index, message, profile
|
||||
from .views import auth, get_message_list, index, message, messages, profile
|
||||
|
||||
urlpatterns = [
|
||||
path("", index),
|
||||
path("auth/", auth),
|
||||
path("messages/", messages),
|
||||
path("u/<str:username>/", profile),
|
||||
path("m/<str:username>/", message),
|
||||
path("api/messages/", get_message_list),
|
||||
path("static/", include("tmessage.views.static")),
|
||||
path("django-admin/", admin.site.urls)
|
||||
]
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
from .templates import auth, index, message, profile # noqa: F401
|
||||
from .api import get_message_list # noqa: F401
|
||||
from .templates import auth, index, message, messages, profile # noqa: F401
|
||||
|
|
56
tmessage/views/api.py
Normal file
56
tmessage/views/api.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
import json
|
||||
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.http import HttpResponse
|
||||
|
||||
from .helper import get_user_object, get_username
|
||||
|
||||
|
||||
def get_message_list(request: WSGIRequest) -> HttpResponse:
|
||||
username = get_username(request)
|
||||
if username is None:
|
||||
return HttpResponse(
|
||||
json.dumps({
|
||||
"success": False
|
||||
}),
|
||||
content_type="application/json",
|
||||
status=401
|
||||
)
|
||||
|
||||
user = get_user_object(username, i_promise_this_user_exists=True)
|
||||
|
||||
queryFilter = {}
|
||||
|
||||
if "offset" in request.GET and (request.GET.get("offset") or "").isdigit():
|
||||
queryFilter["message_id__lt"] = int(request.GET.get("offset") or "")
|
||||
|
||||
if "unread" in request.GET:
|
||||
queryFilter["response"] = None
|
||||
|
||||
if queryFilter:
|
||||
msgObjects = user.received.filter(**queryFilter)
|
||||
else:
|
||||
msgObjects = user.received.all() # type: ignore
|
||||
|
||||
output = []
|
||||
messages = msgObjects.order_by("-message_id")[:50].values_list(
|
||||
"message_id", "content", "response", "anonymous", "u_from"
|
||||
)
|
||||
|
||||
for message in messages:
|
||||
output.append({
|
||||
"id": message[0],
|
||||
"content": message[1],
|
||||
"response": message[2],
|
||||
"from": message[4].username if not message[3] and message[4] else None
|
||||
})
|
||||
|
||||
return HttpResponse(
|
||||
json.dumps({
|
||||
"success": True,
|
||||
"canRespond": True,
|
||||
"messages": output,
|
||||
"more": msgObjects.count() > 50
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
|
@ -23,7 +23,7 @@ def render_template(
|
|||
c = {
|
||||
"accent": random.choice(COLORS),
|
||||
"config": config,
|
||||
"login_token": f"/auth/?sessionid={request.COOKIES.get('sessionid')}"
|
||||
"login_token": f"/auth/?sessionid={request.COOKIES.get('session_id')}"
|
||||
}
|
||||
|
||||
for key, val in context.items():
|
||||
|
|
26
tmessage/views/static.py
Normal file
26
tmessage/views/static.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from django.http import HttpResponse
|
||||
from django.urls import path
|
||||
from django.views.decorators.cache import cache_control
|
||||
|
||||
from tmessage.settings import STATIC_DIR
|
||||
|
||||
|
||||
def get_static_serve(path: str, content_type: str):
|
||||
def x(request):
|
||||
return HttpResponse(
|
||||
open(STATIC_DIR / path, "rb").read(),
|
||||
content_type=content_type
|
||||
)
|
||||
|
||||
x.__name__ = path
|
||||
return x
|
||||
|
||||
file_associations = {
|
||||
"js": "text/javascript",
|
||||
"css": "text/css"
|
||||
}
|
||||
|
||||
urlpatterns = [path(i, cache_control(**{"max-age": 60 * 60 * 24 * 30})(get_static_serve(i, file_associations[i.split(".")[-1]]))) for i in [
|
||||
"js/messages.js",
|
||||
"css/messages.css"
|
||||
]]
|
|
@ -12,9 +12,9 @@ from .helper import (get_user_object, get_username, render_template,
|
|||
def auth(request: WSGIRequest) -> HttpResponseRedirect:
|
||||
resp = HttpResponseRedirect("/")
|
||||
if "remove" in request.GET:
|
||||
resp.set_cookie("sessionid", "", max_age=0, expires=datetime(0, 0, 0))
|
||||
resp.set_cookie("session_id", "", max_age=0, expires=datetime(0, 0, 0))
|
||||
else:
|
||||
resp.set_cookie("sessionid", request.GET.get("sessionid") or "")
|
||||
resp.set_cookie("session_id", request.GET.get("sessionid") or "")
|
||||
|
||||
return resp
|
||||
|
||||
|
@ -74,3 +74,14 @@ def message(request: WSGIRequest, username: str) -> HttpResponse:
|
|||
error=error,
|
||||
self_username=get_username(request)
|
||||
)
|
||||
|
||||
def messages(request: WSGIRequest) -> HttpResponse:
|
||||
username = get_username(request)
|
||||
|
||||
if username is None:
|
||||
return HttpResponseRedirect("/")
|
||||
|
||||
return render_template(
|
||||
request, "messages.html",
|
||||
username=username
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue