Add 'Account->Activity Alerts' page to show content subscriptions

(not sure of best terminology for these - feel free to rename)
This commit is contained in:
freamon 2024-10-02 09:18:27 +00:00
parent ca9b7a523b
commit baaf93087d
10 changed files with 241 additions and 3 deletions

View file

@ -197,6 +197,7 @@
<li><a class="dropdown-item{% if active_child == 'edit_profile' %} active{% endif %}" href="/user/settings">{{ _('Edit profile & settings') }}</a></li>
<li><a class="dropdown-item{% if active_child == 'chats' %} active{% endif %}" href="/chat">{{ _('Chats') }}</a></li>
<li><a class="dropdown-item{% if active_child == 'bookmarks' %} active{% endif %}" href="/bookmarks">{{ _('Bookmarks') }}</a></li>
<li><a class="dropdown-item{% if active_child == 'alerts' %} active{% endif %}" href="/alerts">{{ _('Activity Alerts') }}</a></li>
{% if current_user.is_authenticated and current_user.hide_read_posts -%}
<li><a class="dropdown-item{% if active_child == 'read_posts' %} active{% endif %}" href="/read-posts">{{ _('Read Posts') }}</a></li>
{% endif -%}

View file

@ -0,0 +1,35 @@
{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %}
{% extends 'themes/' + theme() + '/base.html' %}
{% else %}
{% extends "base.html" %}
{% endif %}
{% set active_child = 'alerts' %}
{% block app_content %}
<div class="row">
<div class="col-12 col-md-8 position-relative main_pane">
<nav class="mb-2" aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">{{ _('Home') }}</a></li>
<li class="breadcrumb-item"><a href="/u/{{ user.link() }}">{{ user.display_name() }}</a></li>
<li class="breadcrumb-item active">{{ _('Alerts') }}</li>
</ol>
</nav>
{% include 'user/alerts/_type.html' %}
{% include 'user/alerts/_' + type + '.html' %}
<nav aria-label="Pagination" class="mt-4" role="navigation">
{% if prev_url %}
<a href="{{ prev_url }}" class="btn btn-primary" rel="nofollow">
<span aria-hidden="true">&larr;</span> {{ _('Previous page') }}
</a>
{% endif %}
{% if next_url %}
<a href="{{ next_url }}" class="btn btn-primary" rel="nofollow">
{{ _('Next page') }} <span aria-hidden="true">&rarr;</span>
</a>
{% endif %}
</nav>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% include 'user/alerts/_filter.html' %}
<div class="post_list">
<h2>{{ _('You will be notified of replies to these comments') }}</h2>
{% for reply in entities.items %}
<div class="h-entry post_teaser tabindex="0">
<div class="row">
<div class="col">
<a href="{{ url_for('activitypub.post_ap', post_id=reply.post_id, _anchor='comment_' + str(reply.id)) }}" aria-label="{{ _('Read replies') }}">{{ reply.body }}</a>
</div>
<div class="col col-1">
{% with comment=dict(comment=reply) %}
{% include 'post/_reply_notification_toggle.html' %}
{% endwith %}
</div>
</div>
</div>
{% else %}
<p>{{ _('You have not subscribed to any comments. Use the bell icon on each comment to do so.') }}</p>
{% endfor %}
</div>

View file

@ -0,0 +1,19 @@
{% include 'user/alerts/_filter.html' %}
<div class="post_list">
<h2>{{ _('You will be notified of new posts in these communities') }}</h2>
{% for community in entities.items %}
<div class="h-entry post_teaser tabindex="0">
<div class="row">
<div class="col">
<a href="{{ url_for('activitypub.community_profile', actor=community.ap_id if community.ap_id is not none else community.name) }}" aria-label="{{ _('Visit Community') }}">{{ community.title }}</a>
</div>
<div class="col col-1">
{% include 'community/_notification_toggle.html' %}
</div>
</div>
</div>
{% else %}
<p>{{ _('You have not subscribed to an alert for new content in any communities. Use the bell icon in each community to do so.') }}</p>
{% endfor %}
</div>

View file

@ -0,0 +1,11 @@
<div class="view_filter btn-group mt-1 mb-2" aria-label="{{ _('Alert filters: ') }}">
<a href="/alerts/{{ type }}/all" class="btn {{ 'btn-primary' if filter == 'all' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('All authors') }}">
{{ _('All') }}
</a>
<a href="/alerts/{{ type }}/mine" class="btn {{ 'btn-primary' if filter == 'mine' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Only Mine') }}">
{{ _('Mine') }}
</a>
<a href="/alerts/{{ type }}/others" class="btn {{ 'btn-primary' if filter == 'others' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Only Others') }}">
{{ _('Others') }}
</a>
</div>

View file

@ -0,0 +1,19 @@
{% include 'user/alerts/_filter.html' %}
<div class="post_list">
<h2>{{ _('You will be notified of top-level replies to these posts') }}</h2>
{% for post in entities.items %}
<div class="h-entry post_teaser tabindex="0">
<div class="row">
<div class="col">
<a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}" aria-label="{{ _('Read post') }}">{{ post.title }}</a>
</div>
<div class="col col-1">
{% include 'post/_post_notification_toggle.html' %}
</div>
</div>
</div>
{% else %}
<p>{{ _('You have not subscribed to any posts. Use the bell icon on each post to do so.') }}</p>
{% endfor %}
</div>

View file

@ -0,0 +1,18 @@
<div class="post_list">
<h2>{{ _('You will be notified of new posts in communities covered by these topics') }}</h2>
{% for topic in entities.items -%}
<div class="h-entry post_teaser tabindex="0">
<div class="row">
<div class="col">
<a href="{{ url_for('topic.show_topic', topic_path=topic.machine_name) }}" aria-label="{{ _('Visit Topic') }}">{{ topic.name }}</a>
</div>
<div class="col col-1">
{% include 'topic/_notification_toggle.html' -%}
</div>
</div>
</div>
{% else -%}
<p>{{ _('You have not subscribed to any topics. Use the bell icon on each topic to do so.') }}</p>
{% endfor -%}
</div>

View file

@ -0,0 +1,17 @@
<div class="btn-group mt-1 mb-2" aria-label="{{ _('Alert type: ') }}">
<a href="/alerts/posts/{{ filter }}" class="btn {{ 'btn-primary' if type == 'posts' else 'btn-outline-secondary' }}">
{{ _('Posts') }}
</a>
<a href="/alerts/comments/{{ filter }}" class="btn {{ 'btn-primary' if type == 'comments' else 'btn-outline-secondary' }}">
{{ _('Comments') }}
</a>
<a href="/alerts/communities/{{ filter }}" class="btn {{ 'btn-primary' if type == 'communities' else 'btn-outline-secondary' }}">
{{ _('Communities') }}
</a>
<a href="/alerts/topics/{{ filter }}" class="btn {{ 'btn-primary' if type == 'topics' else 'btn-outline-secondary' }}">
{{ _('Topics') }}
</a>
<a href="/alerts/users/{{ filter }}" class="btn {{ 'btn-primary' if type == 'users' else 'btn-outline-secondary' }}">
{{ _('Users') }}
</a>
</div>

View file

@ -0,0 +1,18 @@
<div class="post_list">
<h2>{{ _('You will be notified of new posts by these users') }}</h2>
{% for user in entities.items %}
<div class="h-entry post_teaser tabindex="0">
<div class="row">
<div class="col">
<a href="{{ url_for('user.show_profile_by_id', user_id=user.id) }}" aria-label="{{ _('Visit User') }}">{{ user.title if user.title else user.user_name }}</a>
</div>
<div class="col col-1">
{% include 'user/_notification_toggle.html' %}
</div>
</div>
</div>
{% else %}
<p>{{ _('You have not subscribed to an alert for new content from any users. Use the bell icon on each person to do so.') }}</p>
{% endfor %}
</div>

View file

@ -12,12 +12,11 @@ from app.activitypub.signature import post_request, default_context
from app.activitypub.util import find_actor_or_create
from app.auth.util import random_token
from app.community.util import save_icon_file, save_banner_file, retrieve_mods_and_backfill
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_PENDING, NOTIF_USER, POST_TYPE_VIDEO, POST_TYPE_LINK, \
POST_TYPE_IMAGE, POST_TYPE_POLL
from app.constants import *
from app.email import send_verification_email
from app.models import Post, Community, CommunityMember, User, PostReply, PostVote, Notification, utcnow, File, Site, \
Instance, Report, UserBlock, CommunityBan, CommunityJoinRequest, CommunityBlock, Filter, Domain, DomainBlock, \
InstanceBlock, NotificationSubscription, PostBookmark, PostReplyBookmark, read_posts
InstanceBlock, NotificationSubscription, PostBookmark, PostReplyBookmark, read_posts, Topic
from app.user import bp
from app.user.forms import ProfileForm, SettingsForm, DeleteAccountForm, ReportUserForm, \
FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm
@ -1143,6 +1142,86 @@ def user_bookmarks_comments():
next_url=next_url, prev_url=prev_url)
@bp.route('/alerts')
@bp.route('/alerts/<type>/<filter>')
@login_required
def user_alerts(type='posts', filter='all'):
page = request.args.get('page', 1, type=int)
low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1'
if type == 'comments':
if filter == 'mine':
entities = PostReply.query.filter_by(deleted=False, user_id=current_user.id).\
join(NotificationSubscription, NotificationSubscription.entity_id == PostReply.id).\
filter_by(type=NOTIF_REPLY, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
elif filter == 'others':
entities = PostReply.query.filter(PostReply.deleted == False, PostReply.user_id != current_user.id).\
join(NotificationSubscription, NotificationSubscription.entity_id == PostReply.id).\
filter_by(type=NOTIF_REPLY, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
else: # default to 'all' filter
entities = PostReply.query.filter_by(deleted=False).\
join(NotificationSubscription, NotificationSubscription.entity_id == PostReply.id).\
filter_by(type=NOTIF_REPLY, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
title = _('Reply Alerts')
elif type == 'communities':
if filter == 'mine':
entities = Community.query.\
join(CommunityMember, CommunityMember.community_id == Community.id).\
filter_by(user_id=current_user.id, is_moderator=True).\
join(NotificationSubscription, NotificationSubscription.entity_id == CommunityMember.community_id).\
filter_by(type=NOTIF_COMMUNITY, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
elif filter == 'others':
entities = Community.query.\
join(CommunityMember, CommunityMember.community_id == Community.id).\
filter_by(user_id=current_user.id, is_moderator=False).\
join(NotificationSubscription, NotificationSubscription.entity_id == CommunityMember.community_id).\
filter_by(type=NOTIF_COMMUNITY, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
else: # default to 'all' filter
entities = Community.query.\
join(NotificationSubscription, NotificationSubscription.entity_id == Community.id).\
filter_by(type=NOTIF_COMMUNITY, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
title = _('Community Alerts')
elif type == 'topics':
# ignore filter
entities = Topic.query.join(NotificationSubscription, NotificationSubscription.entity_id == Topic.id).\
filter_by(type=NOTIF_TOPIC, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
title = _('Topic Alerts')
elif type == 'users':
# ignore filter
entities = User.query.join(NotificationSubscription, NotificationSubscription.entity_id == User.id).\
filter_by(type=NOTIF_USER, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
title = _('User Alerts')
else: # default to 'posts' type
if filter == 'mine':
entities = Post.query.filter_by(deleted=False, user_id=current_user.id).\
join(NotificationSubscription, NotificationSubscription.entity_id == Post.id).\
filter_by(type=NOTIF_POST, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
elif filter == 'others':
entities = Post.query.filter(Post.deleted == False, Post.user_id != current_user.id).\
join(NotificationSubscription, NotificationSubscription.entity_id == Post.id).\
filter_by(type=NOTIF_POST, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
else: # default to 'all' filter
entities = Post.query.filter_by(deleted=False).\
join(NotificationSubscription, NotificationSubscription.entity_id == Post.id).\
filter_by(type=NOTIF_POST, user_id=current_user.id).order_by(desc(NotificationSubscription.created_at))
title = _('Post Alerts')
entities = entities.paginate(page=page, per_page=100 if not low_bandwidth else 50, error_out=False)
next_url = url_for('user.user_alerts', page=entities.next_num, type=type, filter=filter) if entities.has_next else None
prev_url = url_for('user.user_alerts', page=entities.prev_num, type=type, filter=filter) if entities.has_prev and page != 1 else None
return render_template('user/alerts.html', title=title, entities=entities,
low_bandwidth=low_bandwidth, user=current_user, type=type, filter=filter,
moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(), site=g.site,
next_url=next_url, prev_url=prev_url)
@bp.route('/u/<actor>/fediverse_redirect', methods=['GET', 'POST'])
def fediverse_redirect(actor):
actor = actor.strip()