message list

This commit is contained in:
trinkey 2024-12-23 22:38:49 -05:00
parent 103998c0e6
commit 2b34f47754
12 changed files with 222 additions and 9 deletions

View file

@ -1,6 +1,7 @@
{ {
"cSpell.words": [ "cSpell.words": [
"TAUTH", "TAUTH",
"tcommon",
"tmessage" "tmessage"
] ]
} }

View file

@ -19,6 +19,7 @@ SECRET_KEY = config["services"]["message"]["token"]
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_DIR = BASE_DIR / "tmessage/static"
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
INSTALLED_APPS = [ INSTALLED_APPS = [
@ -74,6 +75,4 @@ LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC" TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
USE_TZ = True USE_TZ = True
STATIC_URL = "/static/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
STATIC_ROOT = BASE_DIR / "collected-static"

View 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;
}

View file

@ -0,0 +1,80 @@
let offset;
let timelineElement = document.getElementById("messages");
function escapeHTML(content) {
return content.replaceAll("&", "&amp;").replaceAll("<", "&gt;").replaceAll(">", "&lt;");
}
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); }));

View file

@ -6,7 +6,7 @@
<hr> <hr>
<form method="POST"> <form method="POST">
{% csrf_token %} {% 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 %} {% if self_username %}
Logged in as <b>{{ self_username }}</b> Logged in as <b>{{ self_username }}</b>
<div> <div>

View 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 %}

View file

@ -1,12 +1,15 @@
from django.contrib import admin 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 = [ urlpatterns = [
path("", index), path("", index),
path("auth/", auth), path("auth/", auth),
path("messages/", messages),
path("u/<str:username>/", profile), path("u/<str:username>/", profile),
path("m/<str:username>/", message), path("m/<str:username>/", message),
path("api/messages/", get_message_list),
path("static/", include("tmessage.views.static")),
path("django-admin/", admin.site.urls) path("django-admin/", admin.site.urls)
] ]

View file

@ -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
View 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"
)

View file

@ -23,7 +23,7 @@ def render_template(
c = { c = {
"accent": random.choice(COLORS), "accent": random.choice(COLORS),
"config": config, "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(): for key, val in context.items():

26
tmessage/views/static.py Normal file
View 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"
]]

View file

@ -12,9 +12,9 @@ from .helper import (get_user_object, get_username, render_template,
def auth(request: WSGIRequest) -> HttpResponseRedirect: def auth(request: WSGIRequest) -> HttpResponseRedirect:
resp = HttpResponseRedirect("/") resp = HttpResponseRedirect("/")
if "remove" in request.GET: 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: else:
resp.set_cookie("sessionid", request.GET.get("sessionid") or "") resp.set_cookie("session_id", request.GET.get("sessionid") or "")
return resp return resp
@ -74,3 +74,14 @@ def message(request: WSGIRequest, username: str) -> HttpResponse:
error=error, error=error,
self_username=get_username(request) 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
)