add inbox view

This commit is contained in:
2026-05-20 21:12:05 +03:00
parent 5db63d6907
commit 72172dbb1c
5 changed files with 84 additions and 18 deletions

View File

@@ -63,15 +63,7 @@ class Users(Model):
return db.query(q, self.id, per_page, (page - 1) * per_page)
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)
return Subscriptions.findall({'user_id': self.id})
def can_post_to_thread_or_topic(self, thread_or_topic):
if self.is_guest():
@@ -420,6 +412,13 @@ class Subscriptions(Model):
return res['unread_count']
return None
def get_thread(self):
return Threads.find({'id': self.thread_id})
def get_full_posts_view(self):
q = f'{Posts.FULL_POSTS_QUERY} WHERE posts.thread_id = ? AND posts.created_at > ? ORDER BY posts.created_at ASC'
return db.query(q, self.thread_id, self.last_seen)
class APIRateLimits(Model):
table = 'api_rate_limits'

View File

@@ -148,15 +148,19 @@ def unsubscribe(thread_id):
user = get_active_user()
last_post_id = request.form.get('last_post_id', None)
if last_post_id is None:
return_to = request.form.get('return_to', None)
if return_to is None and last_post_id is None:
abort(400)
elif return_to is None and last_post_id is not None:
return_to = url_for('.thread_by_id', thread_id=thread_id, after=last_post_id)
subscription = Subscriptions.find({'user_id': user.id, 'thread_id': thread_id})
if not subscription:
return redirect(url_for('.thread_by_id', thread_id=thread_id, after=last_post_id))
return redirect(return_to)
subscription.delete()
return redirect(url_for('.thread_by_id', thread_id=thread_id, after=last_post_id))
return redirect(return_to)
@bp.get('/<int:thread_id>/feed.atom/')
def feed(thread_id):

View File

@@ -189,8 +189,10 @@ def settings(username):
@login_required
@redirect_to_own
def inbox(username):
username = username.lower()
return 'stub'
user = get_active_user()
unread_count = user.get_unread_count()
subscriptions = user.get_all_subscriptions()
return render_template('users/inbox.html', unread_count=unread_count, subscriptions=subscriptions)
@bp.get('/<username>/bookmarks/')
@login_required

View File

@@ -0,0 +1,38 @@
{% from 'common/macros.html' import subheader %}
{% from 'common/macros.html' import full_post with context %}
{%- extends 'base.html' -%}
{%- block title -%}inbox{%- endblock -%}
{%- block content -%}
{%- set topline -%}
{%- if unread_count -%}
You have {{unread_count}} unread posts across {{subscriptions | length}} threads.
{%- elif subscriptions | length > 0 -%}
You have no unread posts.
{%- else -%}
You do not have any subscriptions.
{%- endif -%}
{%- endset -%}
{{ subheader('Your inbox', topline) }}
{%- if subscriptions | length > 0 -%}
<div class="plank">
{%- for sub in subscriptions -%}
<details class="separated">
{%- set thread = sub.get_thread() -%}
<summary class="plank secondary-bg no-shadow even">
{{thread.title}} ({{sub.get_unread_count()}} unread)
<form method="POST" action="{{url_for('threads.unsubscribe', thread_id=thread.id)}}">
<input type="hidden" name="return_to" value="{{url_for('users.inbox', username=get_active_user().username)}}">
<a href="{{url_for('threads.thread_by_id', thread_id=thread.id)}}" class="linkbutton">Go to thread</a>
<input type="submit" value="Unsubscribe" class="warn">
</form>
</summary>
{%- set posts = sub.get_full_posts_view() -%}
{%- for post in posts -%}
<div class="plank post no-shadow even">{{full_post(post, show_toolbar=false, show_reactions=false)}}</div>
{%- endfor -%}
</details>
{%- endfor -%}
</div>
{%- endif -%}
{%- endblock -%}

View File

@@ -625,6 +625,33 @@ form.full-width {
justify-content: space-evenly;
}
details {
summary {
cursor: pointer;
display: flex;
align-items: center;
flex-wrap: wrap;
& :last-child {
margin-left: auto;
}
}
&:not([open]) summary::before {
content: '▶';
padding-inline: var(--base-padding);
}
&[open] summary::before {
content: '▼';
padding-inline: var(--base-padding);
}
}
details.separated {
margin: 0.5em 0;
}
/* babycode tags */
.inline-code {
background-color: var(--code-bg-color);
@@ -768,10 +795,6 @@ pre code {
.il { color: #A5D6FF } /* Literal.Number.Integer.Long */
}
summary {
cursor: pointer;
}
p {
margin: 0.5em 0;
}