Compare commits
4 Commits
2345830074
...
bd556d102b
Author | SHA1 | Date | |
---|---|---|---|
bd556d102b | |||
29bb9872d3 | |||
52f6484db1 | |||
df239fb130 |
@ -105,4 +105,11 @@ def create_app():
|
|||||||
def ts_datetime(ts, format):
|
def ts_datetime(ts, format):
|
||||||
return datetime.utcfromtimestamp(ts or int(time.time())).strftime(format)
|
return datetime.utcfromtimestamp(ts or int(time.time())).strftime(format)
|
||||||
|
|
||||||
|
@app.template_filter("pluralize")
|
||||||
|
def pluralize(number, singular = "", plural = "s"):
|
||||||
|
if number == 1:
|
||||||
|
return singular
|
||||||
|
|
||||||
|
return plural
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -4,6 +4,9 @@ from .constants import PermissionLevel
|
|||||||
class Users(Model):
|
class Users(Model):
|
||||||
table = "users"
|
table = "users"
|
||||||
|
|
||||||
|
def get_avatar_url(self):
|
||||||
|
return Avatars.find({"id": self.avatar_id}).file_path
|
||||||
|
|
||||||
def is_guest(self):
|
def is_guest(self):
|
||||||
return self.permission == PermissionLevel.GUEST.value
|
return self.permission == PermissionLevel.GUEST.value
|
||||||
|
|
||||||
@ -53,6 +56,18 @@ class Users(Model):
|
|||||||
WHERE users.id = ?"""
|
WHERE users.id = ?"""
|
||||||
return db.fetch_one(q, self.id)
|
return db.fetch_one(q, self.id)
|
||||||
|
|
||||||
|
def get_all_subscriptions(self):
|
||||||
|
q = """
|
||||||
|
SELECT threads.title AS thread_title, threads.slug AS thread_slug
|
||||||
|
FROM
|
||||||
|
threads
|
||||||
|
JOIN
|
||||||
|
subscriptions ON subscriptions.thread_id = threads.id
|
||||||
|
WHERE
|
||||||
|
subscriptions.user_id = ?"""
|
||||||
|
return db.query(q, self.id)
|
||||||
|
|
||||||
|
|
||||||
class Topics(Model):
|
class Topics(Model):
|
||||||
table = "topics"
|
table = "topics"
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
from flask import (
|
from flask import (
|
||||||
Blueprint, render_template, request, redirect, url_for
|
Blueprint, render_template, request, redirect, url_for, flash
|
||||||
)
|
)
|
||||||
from .users import login_required, mod_only, get_active_user, is_logged_in
|
from .users import login_required, mod_only, get_active_user, is_logged_in
|
||||||
from ..db import db
|
from ..db import db
|
||||||
from ..models import Threads, Topics, Posts
|
from ..models import Threads, Topics, Posts, Subscriptions
|
||||||
|
from ..constants import InfoboxKind
|
||||||
from .posts import create_post
|
from .posts import create_post
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
import math
|
import math
|
||||||
@ -39,7 +40,17 @@ def thread(slug):
|
|||||||
topic = Topics.find({"id": thread.topic_id})
|
topic = Topics.find({"id": thread.topic_id})
|
||||||
other_topics = Topics.select()
|
other_topics = Topics.select()
|
||||||
|
|
||||||
#TODO: subscription last seen
|
is_subscribed = False
|
||||||
|
if is_logged_in():
|
||||||
|
subscription = Subscriptions.find({
|
||||||
|
'thread_id': thread.id,
|
||||||
|
'user_id': get_active_user().id,
|
||||||
|
})
|
||||||
|
if subscription:
|
||||||
|
subscription.update({
|
||||||
|
'last_seen': int(time.time())
|
||||||
|
})
|
||||||
|
is_subscribed = True
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"threads/thread.html",
|
"threads/thread.html",
|
||||||
@ -49,6 +60,7 @@ def thread(slug):
|
|||||||
posts = posts,
|
posts = posts,
|
||||||
topic = topic,
|
topic = topic,
|
||||||
topics = other_topics,
|
topics = other_topics,
|
||||||
|
is_subscribed = is_subscribed,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -107,18 +119,94 @@ def create_form():
|
|||||||
@bp.post("/<slug>/lock")
|
@bp.post("/<slug>/lock")
|
||||||
@login_required
|
@login_required
|
||||||
def lock(slug):
|
def lock(slug):
|
||||||
pass
|
user = get_active_user()
|
||||||
|
thread = Threads.find({'slug': slug})
|
||||||
|
if not ((thread.user_id == user.id) or user.is_mod()):
|
||||||
|
return 'no'
|
||||||
|
target_op = request.form.get('target_op')
|
||||||
|
thread.update({
|
||||||
|
'is_locked': target_op
|
||||||
|
})
|
||||||
|
return redirect(url_for('.thread', slug=slug))
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<slug>/sticky")
|
@bp.post("/<slug>/sticky")
|
||||||
@login_required
|
@login_required
|
||||||
@mod_only(".thread", slug = lambda slug: slug)
|
@mod_only(".thread", slug = lambda slug: slug)
|
||||||
def sticky(slug):
|
def sticky(slug):
|
||||||
pass
|
user = get_active_user()
|
||||||
|
thread = Threads.find({'slug': slug})
|
||||||
|
if not ((thread.user_id == user.id) or user.is_mod()):
|
||||||
|
return 'no'
|
||||||
|
target_op = request.form.get('target_op')
|
||||||
|
thread.update({
|
||||||
|
'is_stickied': target_op
|
||||||
|
})
|
||||||
|
return redirect(url_for('.thread', slug=slug))
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/<slug>/move")
|
@bp.post("/<slug>/move")
|
||||||
@login_required
|
@login_required
|
||||||
@mod_only(".thread", slug = lambda slug: slug)
|
@mod_only(".thread", slug = lambda slug: slug)
|
||||||
def move(slug):
|
def move(slug):
|
||||||
pass
|
user = get_active_user()
|
||||||
|
|
||||||
|
new_topic_id = request.form.get('new_topic_id', default=None)
|
||||||
|
if new_topic_id is None:
|
||||||
|
flash('Thread is already in this topic.', InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.thread', slug=slug))
|
||||||
|
|
||||||
|
new_topic = Topics.find({
|
||||||
|
'id': new_topic_id
|
||||||
|
})
|
||||||
|
if not new_topic:
|
||||||
|
return 'no'
|
||||||
|
thread = Threads.find({
|
||||||
|
'slug': slug
|
||||||
|
})
|
||||||
|
if not thread:
|
||||||
|
return 'no'
|
||||||
|
if new_topic.id == thread.topic_id:
|
||||||
|
flash('Thread is already in this topic.', InfoboxKind.ERROR)
|
||||||
|
return redirect(url_for('.thread', slug=slug))
|
||||||
|
|
||||||
|
old_topic = Topics.find({'id': thread.topic_id})
|
||||||
|
thread.update({'topic_id': new_topic_id})
|
||||||
|
flash(f'Topic moved from "{old_topic.name}" to "{new_topic.name}".', InfoboxKind.INFO)
|
||||||
|
return redirect(url_for('.thread', slug=slug))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/<slug>/subscribe")
|
||||||
|
@login_required
|
||||||
|
def subscribe(slug):
|
||||||
|
user = get_active_user()
|
||||||
|
thread = Threads.find({'slug': slug})
|
||||||
|
if not thread:
|
||||||
|
return 'no'
|
||||||
|
subscription = Subscriptions.find({
|
||||||
|
'user_id': user.id,
|
||||||
|
'thread_id': thread.id,
|
||||||
|
})
|
||||||
|
if request.form['subscribe'] == 'subscribe':
|
||||||
|
if subscription:
|
||||||
|
subscription.delete()
|
||||||
|
Subscriptions.create({
|
||||||
|
'user_id': user.id,
|
||||||
|
'thread_id': thread.id,
|
||||||
|
'last_seen': int(time.time()),
|
||||||
|
})
|
||||||
|
elif request.form['subscribe'] == 'unsubscribe':
|
||||||
|
if not subscription:
|
||||||
|
return 'no'
|
||||||
|
subscription.delete()
|
||||||
|
elif request.form['subscribe'] == 'read':
|
||||||
|
if not subscription:
|
||||||
|
return 'no'
|
||||||
|
subscription.update({
|
||||||
|
'last_seen': int(time.time())
|
||||||
|
})
|
||||||
|
last_visible_post = request.form.get('last_visible_post', default=None)
|
||||||
|
if last_visible_post is not None:
|
||||||
|
return redirect(url_for('.thread', slug=thread.slug, after=last_visible_post))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('users.inbox', username=user.username))
|
||||||
|
@ -2,7 +2,8 @@ from flask import (
|
|||||||
Blueprint, render_template, request, redirect, url_for, flash, session, current_app
|
Blueprint, render_template, request, redirect, url_for, flash, session, current_app
|
||||||
)
|
)
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from ..models import Users, Sessions
|
from ..db import db
|
||||||
|
from ..models import Users, Sessions, Subscriptions
|
||||||
from ..constants import InfoboxKind, PermissionLevel
|
from ..constants import InfoboxKind, PermissionLevel
|
||||||
from ..auth import digest, verify
|
from ..auth import digest, verify
|
||||||
import secrets
|
import secrets
|
||||||
@ -96,6 +97,28 @@ def mod_only(*args, **kwargs):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def admin_only(*args, **kwargs):
|
||||||
|
def decorator(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def wrapper(*view_args, **view_kwargs):
|
||||||
|
if not get_active_user().is_admin():
|
||||||
|
# resolve callables
|
||||||
|
processed_kwargs = {
|
||||||
|
k: v(**view_kwargs) if callable(v) else v
|
||||||
|
for k, v in kwargs.items()
|
||||||
|
}
|
||||||
|
endpoint = args[0] if args else processed_kwargs.get("endpoint")
|
||||||
|
if endpoint.startswith("."):
|
||||||
|
blueprint = current_app.blueprints.get(view_func.__name__.split(".")[0])
|
||||||
|
if blueprint:
|
||||||
|
endpoint = endpoint.lstrip(".")
|
||||||
|
return redirect(url_for(f"{blueprint.name}.{endpoint}", **processed_kwargs))
|
||||||
|
return redirect(url_for(*args, **processed_kwargs))
|
||||||
|
return view_func(*view_args, **view_kwargs)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/log_in")
|
@bp.get("/log_in")
|
||||||
@redirect_if_logged_in(".page", username = lambda: get_active_user().username)
|
@redirect_if_logged_in(".page", username = lambda: get_active_user().username)
|
||||||
def log_in():
|
def log_in():
|
||||||
@ -180,12 +203,160 @@ def settings(username):
|
|||||||
return "stub"
|
return "stub"
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/log_out")
|
||||||
|
@login_required
|
||||||
|
def log_out():
|
||||||
|
user = get_active_user()
|
||||||
|
session_obj = Sessions.find({"key": session['pyrom_session_key']})
|
||||||
|
session_obj.delete()
|
||||||
|
|
||||||
|
session.clear()
|
||||||
|
return redirect(url_for(".log_in"))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/confirm_user/<user_id>")
|
||||||
|
@login_required
|
||||||
|
@mod_only("topics.all_topics")
|
||||||
|
def confirm_user(user_id):
|
||||||
|
target_user = Users.find({"id": user_id})
|
||||||
|
if not target_user:
|
||||||
|
return "no"
|
||||||
|
if int(target_user.permission) > PermissionLevel.GUEST.value:
|
||||||
|
return "no"
|
||||||
|
|
||||||
|
target_user.update({
|
||||||
|
"permission": PermissionLevel.USER.value,
|
||||||
|
"confirmed_on": int(time.time()),
|
||||||
|
})
|
||||||
|
return redirect(url_for(".page", username=target_user.username))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/mod_user/<user_id>")
|
||||||
|
@login_required
|
||||||
|
@admin_only("topics.all_topics")
|
||||||
|
def mod_user(user_id):
|
||||||
|
target_user = Users.find({"id": user_id})
|
||||||
|
if not target_user:
|
||||||
|
return "no"
|
||||||
|
if target_user.is_mod():
|
||||||
|
return "no"
|
||||||
|
|
||||||
|
target_user.update({
|
||||||
|
"permission": PermissionLevel.MODERATOR.value,
|
||||||
|
})
|
||||||
|
return redirect(url_for(".page", username=target_user.username))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/demod_user/<user_id>")
|
||||||
|
@login_required
|
||||||
|
@admin_only("topics.all_topics")
|
||||||
|
def demod_user(user_id):
|
||||||
|
target_user = Users.find({"id": user_id})
|
||||||
|
if not target_user:
|
||||||
|
return "no"
|
||||||
|
if not target_user.is_mod():
|
||||||
|
return "no"
|
||||||
|
|
||||||
|
target_user.update({
|
||||||
|
"permission": PermissionLevel.USER.value,
|
||||||
|
})
|
||||||
|
return redirect(url_for(".page", username=target_user.username))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post("/guest_user/<user_id>")
|
||||||
|
@login_required
|
||||||
|
@admin_only("topics.all_topics")
|
||||||
|
def guest_user(user_id):
|
||||||
|
target_user = Users.find({"id": user_id})
|
||||||
|
if not target_user:
|
||||||
|
return "no"
|
||||||
|
if target_user.is_mod():
|
||||||
|
return "no"
|
||||||
|
|
||||||
|
target_user.update({
|
||||||
|
"permission": PermissionLevel.GUEST.value,
|
||||||
|
})
|
||||||
|
return redirect(url_for(".page", username=target_user.username))
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/<username>/inbox")
|
@bp.get("/<username>/inbox")
|
||||||
@login_required
|
@login_required
|
||||||
def inbox(username):
|
def inbox(username):
|
||||||
return "stub"
|
user = get_active_user()
|
||||||
|
if username != user.username:
|
||||||
|
return redirect(url_for(".inbox", username = user.username))
|
||||||
|
|
||||||
|
new_posts = []
|
||||||
|
subscription = Subscriptions.find({"user_id": user.id})
|
||||||
|
all_subscriptions = None
|
||||||
|
total_unreads_count = None
|
||||||
|
if subscription:
|
||||||
|
all_subscriptions = user.get_all_subscriptions()
|
||||||
|
q = """
|
||||||
|
WITH thread_metadata AS (
|
||||||
|
SELECT
|
||||||
|
posts.thread_id, threads.slug AS thread_slug, threads.title AS thread_title, COUNT(*) AS unread_count, MAX(posts.created_at) AS newest_post_time
|
||||||
|
FROM
|
||||||
|
posts
|
||||||
|
LEFT JOIN
|
||||||
|
threads ON threads.id = posts.thread_id
|
||||||
|
LEFT JOIN
|
||||||
|
subscriptions ON subscriptions.thread_id = posts.thread_id
|
||||||
|
WHERE subscriptions.user_id = ? AND posts.created_at > subscriptions.last_seen
|
||||||
|
GROUP BY posts.thread_id
|
||||||
|
)
|
||||||
|
|
||||||
@bp.post("/log_out")
|
SELECT
|
||||||
def log_out():
|
tm.thread_id, tm.thread_slug, tm.thread_title, tm.unread_count, tm.newest_post_time,
|
||||||
pass
|
|
||||||
|
posts.id, posts.created_at, post_history.content, post_history.edited_at, users.username, users.status, avatars.file_path AS avatar_path, posts.thread_id, users.id AS user_id, post_history.original_markup, users.signature_rendered
|
||||||
|
FROM
|
||||||
|
thread_metadata tm
|
||||||
|
JOIN
|
||||||
|
posts ON posts.thread_id = tm.thread_id
|
||||||
|
JOIN
|
||||||
|
post_history ON posts.current_revision_id = post_history.id
|
||||||
|
JOIN
|
||||||
|
users ON posts.user_id = users.id
|
||||||
|
LEFT JOIN
|
||||||
|
threads ON threads.id = posts.thread_id
|
||||||
|
LEFT JOIN
|
||||||
|
avatars ON users.avatar_id = avatars.id
|
||||||
|
LEFT JOIN
|
||||||
|
subscriptions ON subscriptions.thread_id = posts.thread_id
|
||||||
|
WHERE
|
||||||
|
subscriptions.user_id = ? AND posts.created_at > subscriptions.last_seen
|
||||||
|
ORDER BY
|
||||||
|
tm.newest_post_time DESC, posts.created_at ASC"""
|
||||||
|
new_posts_raw = db.query(q, user.id, user.id)
|
||||||
|
current_thread_id = None
|
||||||
|
current_thread_group = None
|
||||||
|
total_unreads_count = 0
|
||||||
|
for row in new_posts_raw:
|
||||||
|
if row['thread_id'] != current_thread_id:
|
||||||
|
current_thread_group = {
|
||||||
|
'thread_id': row['thread_id'],
|
||||||
|
'thread_title': row['thread_title'],
|
||||||
|
'unread_count': row['unread_count'],
|
||||||
|
'thread_slug': row['thread_slug'],
|
||||||
|
'newest_post_time': row['newest_post_time'],
|
||||||
|
'posts': [],
|
||||||
|
}
|
||||||
|
total_unreads_count += int(row['unread_count'])
|
||||||
|
new_posts.append(current_thread_group)
|
||||||
|
current_thread_id = row['thread_id']
|
||||||
|
current_thread_group['posts'].append({
|
||||||
|
'id': row['id'],
|
||||||
|
'created_at': row['created_at'],
|
||||||
|
'content': row['content'],
|
||||||
|
'edited_at': row['edited_at'],
|
||||||
|
'username': row['username'],
|
||||||
|
'status': row['status'],
|
||||||
|
'avatar_path': row['avatar_path'],
|
||||||
|
'thread_id': row['thread_id'],
|
||||||
|
'user_id': row['user_id'],
|
||||||
|
'original_markup': row['original_markup'],
|
||||||
|
'signature_rendered': row['signature_rendered']
|
||||||
|
})
|
||||||
|
|
||||||
|
return render_template("users/inbox.html", new_posts = new_posts, total_unreads_count = total_unreads_count, all_subscriptions = all_subscriptions)
|
||||||
|
@ -89,7 +89,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro full_post(post, render_sig = True, is_latest = False, editing = False, active_user = None) %}
|
{% macro full_post(post, render_sig = True, is_latest = False, editing = False, active_user = None, no_reply = false) %}
|
||||||
{% set postclass = "post" %}
|
{% set postclass = "post" %}
|
||||||
{% if editing %}
|
{% if editing %}
|
||||||
{% set postclass = postclass + " editing" %}
|
{% set postclass = postclass + " editing" %}
|
||||||
|
@ -20,16 +20,21 @@
|
|||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
{% if can_subscribe %}
|
{% if can_subscribe %}
|
||||||
|
<form class="modform" action="{{ url_for('threads.subscribe', slug=thread.slug) }}" method="post">
|
||||||
|
<input type='hidden' name='last_visible_post' value='{{posts[-1].id}}'>
|
||||||
|
<input type='hidden' name='subscribe' value='{{ 'unsubscribe' if is_subscribed else 'subscribe' }}'>
|
||||||
|
<input type='submit' value='{{ 'Unsubscribe' if is_subscribed else 'Subscribe' }}'>
|
||||||
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_lock %}
|
{% if can_lock %}
|
||||||
<form class="modform" action="{{ url_for("threads.lock", slug=thread.slug) }}" method="post">
|
<form class="modform" action="{{ url_for("threads.lock", slug=thread.slug) }}" method="post">
|
||||||
<input type=hidden value="{{ (not thread.is_locked) | int }}">
|
<input type=hidden name='target_op' value="{{ (not thread.is_locked) | int }}">
|
||||||
<input class="warn" type="submit" value="{{"Unlock thread" if thread.is_locked else "Lock thread"}}">
|
<input class="warn" type="submit" value="{{"Unlock thread" if thread.is_locked else "Lock thread"}}">
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if active_user.is_mod() %}
|
{% if active_user.is_mod() %}
|
||||||
<form class="modform" action="{{ url_for("threads.sticky", slug=thread.slug) }}" method="post">
|
<form class="modform" action="{{ url_for("threads.sticky", slug=thread.slug) }}" method="post">
|
||||||
<input type=hidden value="{{ (not thread.is_stickied) | int }}">
|
<input type=hidden name='target_op' value="{{ (not thread.is_stickied) | int }}">
|
||||||
<input class="warn" type="submit" value="{{"Unsticky thread" if thread.is_stickied else "Sticky thread"}}">
|
<input class="warn" type="submit" value="{{"Unsticky thread" if thread.is_stickied else "Sticky thread"}}">
|
||||||
</form>
|
</form>
|
||||||
<form class="modform" action="{{ url_for("threads.move", slug=thread.slug) }}" method="post">
|
<form class="modform" action="{{ url_for("threads.move", slug=thread.slug) }}" method="post">
|
||||||
|
51
app/templates/users/inbox.html
Normal file
51
app/templates/users/inbox.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% from "common/macros.html" import timestamp, full_post %}
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}inbox{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="inbox-container">
|
||||||
|
{% if all_subscriptions is none %}
|
||||||
|
You have no subscriptions.<br>
|
||||||
|
{% else %}
|
||||||
|
Your subscriptions:
|
||||||
|
<ul>
|
||||||
|
{% for sub in all_subscriptions %}
|
||||||
|
<li>
|
||||||
|
<a href=" {{ url_for("threads.thread", slug=sub.thread_slug) }} ">{{ sub.thread_title }}</a>
|
||||||
|
<form class="modform" method="post" action="{{ url_for("threads.subscribe", slug = sub.thread_slug) }}">
|
||||||
|
<input type="hidden" name="subscribe" value="unsubscribe">
|
||||||
|
<input class="warn" type="submit" value="Unsubscribe">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% if not new_posts %}
|
||||||
|
You have no unread posts.
|
||||||
|
{% else %}
|
||||||
|
You have {{ total_unreads_count }} unread post{{(total_unreads_count | int) | pluralize }}:
|
||||||
|
{% for thread in new_posts %}
|
||||||
|
<div class="accordion">
|
||||||
|
<div class="accordion-header">
|
||||||
|
<button type="button" class="accordion-toggle">▼</button>
|
||||||
|
{% set latest_post_id = thread.posts[-1].id %}
|
||||||
|
{% set unread_posts_text = " (" + (thread.unread_count | string) + (" unread post" | pluralize) %}
|
||||||
|
<a class="accordion-title" href="{{ url_for("threads.thread", slug=latest_post_slug, after=latest_post_id, _anchor="post-" + (latest_post_id | string)) }}" title="Jump to latest post">{{thread.thread_title + unread_posts_text}}, latest at {{ timestamp(thread.newest_post_time) }})</a>
|
||||||
|
<form class="modform" method="post" action="{{ url_for("threads.subscribe", slug = thread.thread_slug) }}">
|
||||||
|
<input type="hidden" name="subscribe" value="read">
|
||||||
|
<input type="submit" value="Mark thread as Read">
|
||||||
|
</form>
|
||||||
|
<form class="modform" method="post" action="{{ url_for("threads.subscribe", slug = thread.thread_slug) }}">
|
||||||
|
<input type="hidden" name="subscribe" value="unsubscribe">
|
||||||
|
<input class="warn" type="submit" value="Unsubscribe">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-content">
|
||||||
|
{% for post in thread.posts %}
|
||||||
|
{{ full_post(post, no_reply = true) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -19,18 +19,74 @@
|
|||||||
<h1 class="thread-title">Moderation controls</h1>
|
<h1 class="thread-title">Moderation controls</h1>
|
||||||
{% if target_user.is_guest() %}
|
{% if target_user.is_guest() %}
|
||||||
<p>This user is a guest. They signed up on {{ timestamp(target_user['created_at']) }}</p>
|
<p>This user is a guest. They signed up on {{ timestamp(target_user['created_at']) }}</p>
|
||||||
|
<form class="modform" method="post" action="{{ url_for("users.confirm_user", user_id=target_user.id) }}">
|
||||||
|
<input type="submit" value="Confirm user">
|
||||||
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>This user signed up on {{ timestamp(target_user['created_at']) }} and was confirmed on {{ timestamp(target_user['confirmed_on']) }}</p>
|
<p>This user signed up on {{ timestamp(target_user['created_at']) }} and was confirmed on {{ timestamp(target_user['confirmed_on']) }}</p>
|
||||||
|
{% if (target_user.permission | int) < (active_user.permission | int) %}
|
||||||
|
<form class="modform" method="post" action="{{ url_for("users.guest_user", user_id=target_user.id) }}">
|
||||||
|
<input class="warn" type="submit" value="Demote user to guest (soft ban)">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% if active_user.is_admin() and not target_user.is_mod() %}
|
||||||
|
<form class="modform" method="post" action="{{ url_for("users.mod_user", user_id=target_user.id) }}">
|
||||||
|
<input class="warn" type="submit" value="Promote user to moderator">
|
||||||
|
</form>
|
||||||
|
{% elif target_user.is_mod() and (target_user.permission | int) < (active_user.permission | int) %}
|
||||||
|
<form class="modform" method="post" action="{{ url_for("users.demod_user", user_id=target_user.id) }}">
|
||||||
|
<input class="critical" type="submit" value="Demote user to regular user">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="user-info">
|
||||||
{% with stats = target_user.get_post_stats() %}
|
<div class="user-page-usercard">
|
||||||
<ul>
|
<div class="usercard-inner">
|
||||||
<li>Posts created: {{ stats.post_count }}</li>
|
<img class="avatar" src="{{ target_user.get_avatar_url() }}">
|
||||||
<li>Threads started: {{ stats.thread_count }}</li>
|
<strong class="big">{{ target_user.username }}</strong>
|
||||||
<li>Latest started thread: {{ stats.latest_thread_title }}
|
{% if target_user.status %}
|
||||||
</ul>
|
<em class="user-status">{{ target_user.status }}</em>
|
||||||
{% endwith %}
|
{% endif %}
|
||||||
|
{% if target_user.signature_rendered %}
|
||||||
|
Signature:
|
||||||
|
<div>{{ target_user.signature_rendered | safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-page-stats">
|
||||||
|
{% with stats = target_user.get_post_stats() %}
|
||||||
|
<ul class="user-stats-list">
|
||||||
|
<li>Permission: {{ target_user.permission }}</li>
|
||||||
|
<li>Posts created: {{ stats.post_count }}</li>
|
||||||
|
<li>Threads started: {{ stats.thread_count }}</li>
|
||||||
|
{% if stats.latest_thread_title %}
|
||||||
|
<li>Latest started thread: <a href="{{ url_for("threads.thread", slug = stats.latest_thread_slug) }}">{{ stats.latest_thread_title }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endwith %}
|
||||||
|
Latest posts:
|
||||||
|
{% with posts = target_user.get_latest_posts() %}
|
||||||
|
<div class="user-page-posts">
|
||||||
|
{% for post in posts %}
|
||||||
|
<div class="post-content-container">
|
||||||
|
<div class="post-info">
|
||||||
|
<a href="{{ url_for("threads.thread", slug=post.thread_slug, after=post.id) }}" 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>
|
||||||
|
</div>
|
||||||
|
<div class="post-content wider user-page-post-preview">
|
||||||
|
<div class="post-inner">{{ post.content | safe }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user