add thread view

This commit is contained in:
Lera Elvoé 2025-07-01 18:11:16 +03:00
parent e39ccd5939
commit e5a140fa2e
Signed by: yagich
SSH Key Fingerprint: SHA256:6xjGb6uA7lAVcULa7byPEN//rQ0wPoG+UzYVMfZnbvc
3 changed files with 215 additions and 3 deletions

View File

@ -1,10 +1,12 @@
from flask import ( from flask import (
Blueprint, render_template, request, redirect, url_for Blueprint, render_template, request, redirect, url_for
) )
from .users import login_required, get_active_user from .users import login_required, mod_only, get_active_user, is_logged_in
from ..models import Threads, Topics from ..db import db
from ..models import Threads, Topics, Posts
from .posts import create_post from .posts import create_post
from slugify import slugify from slugify import slugify
import math
import time import time
bp = Blueprint("threads", __name__, url_prefix = "/threads/") bp = Blueprint("threads", __name__, url_prefix = "/threads/")
@ -12,7 +14,43 @@ bp = Blueprint("threads", __name__, url_prefix = "/threads/")
@bp.get("/<slug>") @bp.get("/<slug>")
def thread(slug): def thread(slug):
return slug POSTS_PER_PAGE = 10
thread = Threads.find({"slug": slug})
if not thread:
return "no"
post_count = Posts.count({"thread_id": thread.id})
page_count = max(math.ceil(post_count / POSTS_PER_PAGE), 1)
page = 1
after = request.args.get("after", default=None)
if after is not None:
after_id = int(after)
post_position = Posts.count([
("thread_id", "=", thread.id),
("id", "<=", after_id),
])
print(post_position)
page = math.floor((post_position + 1) / POSTS_PER_PAGE) + 1
else:
page = max(1, min(page_count, int(request.args.get("page", default = 1))))
posts = thread.get_posts(POSTS_PER_PAGE, (page - 1) * POSTS_PER_PAGE)
print(posts)
topic = Topics.find({"id": thread.topic_id})
other_topics = Topics.select()
#TODO: subscription last seen
return render_template(
"threads/thread.html",
thread = thread,
current_page = page,
page_count = page_count,
posts = posts,
topic = topic,
topics = other_topics,
)
@bp.get("/create") @bp.get("/create")
@ -47,3 +85,23 @@ def create_form():
}) })
post = create_post(thread.id, user.id, post_content) post = create_post(thread.id, user.id, post_content)
return redirect(url_for(".thread", slug = thread.slug)) return redirect(url_for(".thread", slug = thread.slug))
@bp.post("/<slug>/lock")
@login_required
def lock(slug):
pass
@bp.post("/<slug>/sticky")
@login_required
@mod_only(".thread", slug = lambda slug: slug)
def sticky(slug):
pass
@bp.post("/<slug>/move")
@login_required
@mod_only(".thread", slug = lambda slug: slug)
def move(slug):
pass

View File

@ -88,3 +88,86 @@
</span> </span>
</form> </form>
{% endmacro %} {% endmacro %}
{% macro full_post(post, render_sig = True, is_latest = False, editing = False, active_user = None) %}
{% set postclass = "post" %}
{% if editing %}
{% set postclass = postclass + " editing" %}
{% endif %}
<div class=" {{ postclass }}" id="post-{{ post['id'] }}">
<div class="usercard">
<div class="usercard-inner">
<a href="{{ url_for("users.page", username=post['username']) }}" style="display: contents;">
<img src="{{ post['avatar_path'] }}" class="avatar">
</a>
<a href="{{ url_for("users.page", username=post['username']) }}" class="username-link">{{ post['username'] }}</a>
{% if post['status'] %}
<em class="user-status">{{ post['status'] }}</em>
{% endif %}
</div>
</div>
<div class="post-content-container" {{ "id=latest-post" if is_latest else "" }}>
<div class="post-info">
{% set post_permalink = url_for("threads.thread", slug = post['thread_slug'], after = post['id'], _anchor = ("post-" + (post['id'] | string))) %}
<a href="{{ post_permalink }}" title="Permalink"><i>
{% if (post['edited_at'] | int) > (post['created_at'] | int) %}
Edited on {{ timestamp(post['edited_at']) }}
{% else %}
Posted on {{ timestamp(post['edited_at']) }}
{% endif %}
</i></a>
<span>
{% set show_edit = false %}
{% if active_user %}
{% set show_edit = (active_user.id | string) == (post['user_id'] | string) and (not post['thread_is_locked'] or active_user.is_mod()) and not no_reply %}
{% endif %}
{% if show_edit %}
<a class="linkbutton" href="#TODO">Edit</a>
{% endif %}
{% set show_reply = true %}
{% if active_user and post['thread_is_locked'] and not active_user.is_mod() %}
{% set show_reply = false %}
{% elif active_user and active_user.is_guest() %}
{% set show_reply = false %}
{% elif editing %}
{% set show_reply = false %}
{% elif no_reply %}
{% set show_reply = false %}
{% endif %}
{% if show_reply %}
{% set qtext = "[url=%s]%s said:[/url]" | format(post_permalink, post['username']) %}
{% set reply_text = "%s\n[quote]%s[/quote]\n" | format(qtext, post['original_markup']) %}
<button value="{{ reply_text }}" class="reply-button">Quote</button>
{% endif %}
{% set show_delete = false %}
{% if active_user %}
{% set show_delete = (((post['user_id'] | string) == (active_user.id | string) and not post['thread_is_locked']) or active_user.is_mod()) and not no_reply %}
{% endif %}
{% if show_delete %}
<button class="critical post-delete-button" value="{{ post['id'] }}">Delete</button>
{% endif %}
</span>
</div>
<div class="post-content">
{% if not editing %}
<div class="post-inner">{{ post['content'] | safe }}</div>
{% if render_sig and post['signature_rendered'] %}
<div class="signature-container">
<hr>
{{ post['signature_rendered'] | safe }}
</div>
{% endif %}
{% else %}
{{ babycode_editor_form(cancel_url = post_permalink, prefill = post['original_markup'], ta_name = "new_content") }}
{% endif %}
</div>
</div>
</div>
{% endmacro %}

View File

@ -0,0 +1,71 @@
{% from 'common/macros.html' import pager, babycode_editor_form, full_post %}
{% extends "base.html" %}
{% block title %}{{ thread.title }}{% endblock %}
{% block content %}
{% set can_post = false %}
{% set can_lock = false %}
{% set can_subscribe = false %}
{% if active_user %}
{% set can_subscribe = true %}
{% set can_post = (not thread.is_locked and not active_user.is_guest()) or active_user.is_mod() %}
{% set can_lock = ((active_user.id | int) == (thread.user_id | int)) or active_user.is_mod() %}
{% endif %}
<main>
<nav class="darkbg">
<h1 class="thread-title">{{ thread.title }}</h1>
<span>Posted in <a href="{{ url_for("topics.topic", slug=topic.slug) }}">{{ topic.name }}</a>
{% if thread.is_stickied %}
&bullet; <i>stickied, so it's probably important</i>
{% endif %}
</span>
<div>
{% if can_subscribe %}
{% endif %}
{% if can_lock %}
<form class="modform" action="{{ url_for("threads.lock", slug=thread.slug) }}" method="post">
<input type=hidden value="{{ (not thread.is_locked) | int }}">
<input class="warn" type="submit" value="{{"Unlock thread" if thread.is_locked else "Lock thread"}}">
</form>
{% endif %}
{% if active_user.is_mod() %}
<form class="modform" action="{{ url_for("threads.sticky", slug=thread.slug) }}" method="post">
<input type=hidden value="{{ (not thread.is_stickied) | int }}">
<input class="warn" type="submit" value="{{"Unsticky thread" if thread.is_stickied else "Sticky thread"}}">
</form>
<form class="modform" action="{{ url_for("threads.move", slug=thread.slug) }}" method="post">
<label for="new_topic_id">Move to topic:</label>
<select style="width:200px;" id="new_topic_id" name="new_topic_id" autocomplete="off">
{% for topic in topics %}
<option value="{{ topic['id'] }}" {{ "selected disabled" if (thread.topic_id | string) == (topic['id'] | string) else "" }}>{{ topic['name'] }}</option>
{% endfor %}
</select>
<input class="warn" type="submit" value="Move thread">
</form>
{% endif %}
</div>
</nav>
{% for post in posts %}
{{ full_post(post = post, active_user = active_user, is_latest = loop.index0 == (posts | length)) }}
{% endfor %}
</main>
<nav id="bottomnav">
{{ pager(current_page = current_page, page_count = page_count) }}
</nav>
{% if can_post %}
<h1>Respond to "{{ thread.title }}"</h1>
{{ babycode_editor_form(ta_name = post_content)}}
{% endif %}
{% endblock %}
<dialog id="delete-dialog">
<div class=delete-dialog-inner>
Are you sure you want to delete the highlighted post?
<span>
<button id=post-delete-dialog-close>Cancel</button>
<button class="critical" form=post-delete-form>Delete</button>
<form id="post-delete-form" method="post"></form>
</span>
</div>
</dialog>
<script src="/static/js/thread.js?v=1"></script>