fix overflow + editing + post list order
This commit is contained in:
parent
8a648d23c4
commit
616dcd49a1
12 changed files with 175 additions and 57 deletions
0
manage.py
Executable file → Normal file
0
manage.py
Executable file → Normal file
18
tblog/migrations/0002_tbpost_edited_at.py
Normal file
18
tblog/migrations/0002_tbpost_edited_at.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 5.1.4 on 2025-01-04 20:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tblog', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tbpost',
|
||||
name='edited_at',
|
||||
field=models.IntegerField(null=True),
|
||||
),
|
||||
]
|
|
@ -20,6 +20,7 @@ class tBPost(models.Model):
|
|||
title = models.TextField(max_length=1_000)
|
||||
content = models.TextField(max_length=500_000)
|
||||
timestamp = models.IntegerField()
|
||||
edited_at = models.IntegerField(null=True)
|
||||
text_format = models.CharField(max_length=10) # plain, mono, markdown, html ... (more to come?)
|
||||
|
||||
class Meta:
|
||||
|
|
16
tblog/static/css/blog.css
Normal file
16
tblog/static/css/blog.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
#real-blog-container[data-blog-format="mono"] pre,
|
||||
#real-blog-container[data-blog-format="plain"] pre {
|
||||
white-space: pre-wrap;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
#real-blog-container[data-blog-format="html"],
|
||||
#real-blog-container[data-blog-format="markdown"] {
|
||||
width: 100%;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
#blog-container {
|
||||
max-width: 1000px;
|
||||
width: calc(90vw - 40px);
|
||||
}
|
|
@ -8,8 +8,18 @@
|
|||
{% block body %}
|
||||
<h1>{{ blog.title }}</h1>
|
||||
<div><b>By <a href="/blog/{{ blog.u_by.username }}">{{ blog.u_by.username }}</a></b></div>
|
||||
<small data-timestamp="{{ blog.timestamp }}"></small>
|
||||
|
||||
<small>
|
||||
<span data-timestamp="{{ blog.timestamp }}"></span>
|
||||
{% if blog.edited_at %}
|
||||
- Edited
|
||||
<span data-timestamp="{{ blog.edited_at }}"></span>
|
||||
{% endif %}
|
||||
</small>
|
||||
|
||||
{% if username == blog.u_by.username %}
|
||||
<p><a href="/blog/{{ username }}/{{ blog.url }}/edit/">Edit post</a></p>
|
||||
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
|
@ -20,7 +30,7 @@
|
|||
</form>
|
||||
{% endif %}
|
||||
<hr>
|
||||
<div id="blog-container" style="max-width: 1000px; width: 90vw;" class="inline-block left" data-format="{{ blog.text_format }}" data-raw="{{ blog.content }}"><i>Loading...</i></div>
|
||||
<div id="blog-container" class="inline-block left" data-format="{{ blog.text_format }}" data-raw="{{ blog.content }}"><i>Loading...</i></div>
|
||||
|
||||
<script>
|
||||
const blog = document.getElementById("blog-container");
|
||||
|
|
|
@ -4,16 +4,18 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.37.1/ace.min.js"></script>
|
||||
{% endif %}
|
||||
|
||||
<link rel="stylesheet" href="/static/css/blog.css">
|
||||
|
||||
<script>
|
||||
function toHTML(format, raw) {
|
||||
if (format == "markdown") {
|
||||
return DOMPurify.sanitize(marked.parse(raw));
|
||||
return `<div id="real-blog-container" data-blog-format="markdown">${DOMPurify.sanitize(marked.parse(raw))}</div>`;
|
||||
} else if (format == "html") {
|
||||
return DOMPurify.sanitize(raw);
|
||||
return `<div id="real-blog-container" data-blog-format="html">${DOMPurify.sanitize(raw)}</div>`;
|
||||
} else if (format == "mono") {
|
||||
return `<pre class="no-margin">${raw.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """)}</pre>`;
|
||||
return `<div id="real-blog-container" data-blog-format="mono"><pre class="no-margin">${raw.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """)}</pre></div>`;
|
||||
} else {
|
||||
return `<pre class="not-code no-margin">${raw.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """)}</pre>`;
|
||||
return `<div id="real-blog-container" data-blog-format="plain"><pre class="not-code no-margin">${raw.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """)}</pre></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
max-width: 600px;
|
||||
border: 1px solid rgb(var(--accent));
|
||||
border-radius: 10px;
|
||||
margin-top: 18px;
|
||||
margin: 18px 0;
|
||||
padding: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -31,7 +31,13 @@
|
|||
<hr>
|
||||
<a class="fake-link blog-post" href="/blog/{{ username }}/{{ post.url }}/">
|
||||
<h2 class="no-margin">{{ post.title }}</h2>
|
||||
<small data-timestamp="{{ post.timestamp }}"></small>
|
||||
<small>
|
||||
<span data-timestamp="{{ post.timestamp }}"></span>
|
||||
{% if post.edited_at %}
|
||||
- Edited
|
||||
<span data-timestamp="{{ post.edited_at }}"></span>
|
||||
{% endif %}
|
||||
</small>
|
||||
</a>
|
||||
{% empty %}
|
||||
<hr>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
{% if error %}<p class="error">{{ error }}</p>{% endif %}
|
||||
|
||||
<p>Characters: <b><span id="character-count" data-localize-number="0">0</span>/<span data-localize-number="500000">500000</span></b></p>
|
||||
<p>Characters: <b><span id="character-count">0</span>/<span data-localize-number="500000">500000</span></b></p>
|
||||
<p><b>Configuration:</b></p>
|
||||
|
||||
<p>
|
||||
|
@ -43,12 +43,10 @@
|
|||
</p>
|
||||
|
||||
<p>
|
||||
<label for="url">{{ config.services.blog.url.pub }}/blog/{{ username }}/</label
|
||||
><input maxlength="1000" placeholder="blog-url" required name="url" id="url" value="{{ repopulate.url }}">
|
||||
<label for="url">{{ config.services.blog.url.pub }}/blog/{{ username }}/</label><input maxlength="1000" placeholder="blog-url" required name="url" id="url" value="{{ repopulate.url }}">
|
||||
</p>
|
||||
|
||||
<p><input type="submit" value="Create Blog Post"></p>
|
||||
<small>(blog posts can't be changed after posting)</small>
|
||||
<p><input type="submit" value="{% if editing %}Confirm Edits{% else %}Create Blog Post{% endif %}"></p>
|
||||
</form>
|
||||
|
||||
<script src="/static/js/write.js?v={{ config.version_str }}"></script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
from .views import auth, index, user, view_blog, write
|
||||
from .views import auth, edit_blog, index, user, view_blog, write
|
||||
|
||||
urlpatterns = [
|
||||
path("", index),
|
||||
|
@ -9,6 +9,7 @@ urlpatterns = [
|
|||
path("create/", write),
|
||||
path("blog/<str:username>/", user),
|
||||
path("blog/<str:username>/<str:url>/", view_blog),
|
||||
path("blog/<str:username>/<str:url>/edit/", edit_blog),
|
||||
path("static/", include("tblog.views.static")),
|
||||
path("django-admin/", admin.site.urls)
|
||||
]
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
from .templates import auth, index, user, view_blog, write # noqa: F401
|
||||
from .templates import (auth, edit_blog, index, user, view_blog, # noqa: F401
|
||||
write)
|
||||
|
|
|
@ -22,6 +22,7 @@ file_associations = {
|
|||
|
||||
urlpatterns = [path(i, cache_control(**{"max-age": 60 * 60 * 24 * 30})(get_static_serve(i, file_associations[i.split(".")[-1]]))) for i in [
|
||||
"css/ace.css",
|
||||
"css/blog.css",
|
||||
"css/write.css",
|
||||
"js/write.js"
|
||||
]]
|
||||
|
|
|
@ -38,51 +38,51 @@ def index(request: WSGIRequest) -> HttpResponse:
|
|||
|
||||
def write(request: WSGIRequest) -> HttpResponse:
|
||||
username = get_username(request)
|
||||
if username:
|
||||
repopulate = {}
|
||||
error = None
|
||||
|
||||
if request.method == "POST":
|
||||
url = (request.POST.get("url") or "").strip().replace(" ", "-").lower()
|
||||
title = (request.POST.get("title") or "").strip()
|
||||
content = (request.POST.get("raw") or "").strip()
|
||||
fmt = request.POST.get("format")
|
||||
|
||||
repopulate = {
|
||||
"url": url,
|
||||
"title": title,
|
||||
"content": content,
|
||||
"format": fmt
|
||||
}
|
||||
|
||||
if not (url and title and content) or fmt not in ["plain", "mono", "markdown", "html"] or len(url) > 250 or len(title) > 1_000 or len(content) > 500_000:
|
||||
error = "Invalid input(s)"
|
||||
|
||||
else:
|
||||
try:
|
||||
tBPost.objects.create(
|
||||
u_by=get_user_object(username, i_promise_this_user_exists=True),
|
||||
url=request.POST.get("url"),
|
||||
title=request.POST.get("title"),
|
||||
content=request.POST.get("raw"),
|
||||
timestamp=math.floor(time.time()),
|
||||
text_format=request.POST.get("format")
|
||||
)
|
||||
except IntegrityError:
|
||||
error = f"Url '{url}' already in use"
|
||||
else:
|
||||
return HttpResponseRedirect(f"/blog/{username}/{escape_url(url)}/")
|
||||
|
||||
if not username:
|
||||
return render_template(
|
||||
request, "write.html",
|
||||
title="Writing",
|
||||
username=username,
|
||||
error=error,
|
||||
repopulate=repopulate
|
||||
request, "noauth/index.html"
|
||||
)
|
||||
|
||||
repopulate = {}
|
||||
error = None
|
||||
|
||||
if request.method == "POST":
|
||||
url = (request.POST.get("url") or "").strip().replace(" ", "-").lower()
|
||||
title = (request.POST.get("title") or "").strip()
|
||||
content = (request.POST.get("raw") or "").strip()
|
||||
fmt = request.POST.get("format")
|
||||
|
||||
repopulate = {
|
||||
"url": url,
|
||||
"title": title,
|
||||
"content": content,
|
||||
"format": fmt
|
||||
}
|
||||
|
||||
if not (url and title and content) or fmt not in ["plain", "mono", "markdown", "html"] or len(url) > 250 or len(title) > 1_000 or len(content) > 500_000:
|
||||
error = "Invalid input(s)"
|
||||
|
||||
else:
|
||||
try:
|
||||
tBPost.objects.create(
|
||||
u_by=get_user_object(username, i_promise_this_user_exists=True),
|
||||
url=url,
|
||||
title=title,
|
||||
content=content,
|
||||
timestamp=math.floor(time.time()),
|
||||
text_format=fmt
|
||||
)
|
||||
except IntegrityError:
|
||||
error = f"Url '{url}' already in use"
|
||||
else:
|
||||
return HttpResponseRedirect(f"/blog/{username}/{escape_url(url)}/")
|
||||
|
||||
return render_template(
|
||||
request, "noauth/index.html"
|
||||
request, "write.html",
|
||||
title="Writing",
|
||||
username=username,
|
||||
error=error,
|
||||
repopulate=repopulate
|
||||
)
|
||||
|
||||
def view_blog(request: WSGIRequest, username: str, url: str) -> HttpResponse:
|
||||
|
@ -109,6 +109,70 @@ def view_blog(request: WSGIRequest, username: str, url: str) -> HttpResponse:
|
|||
username=self_username
|
||||
)
|
||||
|
||||
def edit_blog(request: WSGIRequest, username: str, url: str) -> HttpResponse:
|
||||
try:
|
||||
blog = tBPost.objects.get(
|
||||
u_by=get_user_object(username),
|
||||
url=url
|
||||
)
|
||||
except tBPost.DoesNotExist:
|
||||
return render_template(
|
||||
request, "404.html"
|
||||
)
|
||||
|
||||
self_username = get_username(request)
|
||||
|
||||
if username != self_username:
|
||||
return render_template(
|
||||
request, "404.html"
|
||||
)
|
||||
|
||||
error = None
|
||||
repopulate = {
|
||||
"url": blog.url,
|
||||
"title": blog.title,
|
||||
"content": blog.content,
|
||||
"format": blog.text_format
|
||||
}
|
||||
|
||||
if request.method == "POST":
|
||||
url = (request.POST.get("url") or "").strip().replace(" ", "-").lower() or blog.url
|
||||
title = (request.POST.get("title") or "").strip() or blog.title
|
||||
content = (request.POST.get("raw") or "").strip() or blog.content
|
||||
fmt = request.POST.get("format") or blog.text_format
|
||||
|
||||
repopulate = {
|
||||
"url": url,
|
||||
"title": title,
|
||||
"content": content,
|
||||
"format": fmt
|
||||
}
|
||||
|
||||
if not (url and title and content) or fmt not in ["plain", "mono", "markdown", "html"] or len(url) > 250 or len(title) > 1_000 or len(content) > 500_000:
|
||||
error = "Invalid input(s)"
|
||||
|
||||
else:
|
||||
try:
|
||||
blog.url = url
|
||||
blog.title = title
|
||||
blog.content = content
|
||||
blog.edited_at = math.floor(time.time())
|
||||
blog.text_format = fmt
|
||||
blog.save()
|
||||
except IntegrityError:
|
||||
error = f"Url '{url}' already in use"
|
||||
else:
|
||||
return HttpResponseRedirect(f"/blog/{username}/{escape_url(url)}/")
|
||||
|
||||
return render_template(
|
||||
request, "write.html",
|
||||
title="Editing",
|
||||
editing=True,
|
||||
username=username,
|
||||
error=error,
|
||||
repopulate=repopulate
|
||||
)
|
||||
|
||||
def user(request: WSGIRequest, username: str) -> HttpResponse:
|
||||
user = get_user_object(username)
|
||||
|
||||
|
@ -117,7 +181,7 @@ def user(request: WSGIRequest, username: str) -> HttpResponse:
|
|||
request, "user.html",
|
||||
username=user.username,
|
||||
self_username=get_username(request),
|
||||
posts=user.posts.all() # type: ignore
|
||||
posts=user.posts.order_by("-id") # type: ignore
|
||||
)
|
||||
|
||||
return render_template(
|
||||
|
|
Loading…
Reference in a new issue