Merge remote-tracking branch 'origin/main'

This commit is contained in:
rimu 2024-10-04 18:45:48 +13:00
commit 43a09e14a9
17 changed files with 289 additions and 66 deletions

View file

@ -359,8 +359,7 @@ h1 .fe-bell, h1 .fe-no-bell {
top: 2px; top: 2px;
} }
.fe-poll:before { .fe-poll:before {
content: "\e91b"; content: "\e91b"; /* possibly e985 */
/* possibly e985 */
} }
.fe-sticky-left::before { .fe-sticky-left::before {
@ -513,25 +512,20 @@ a {
.skip-link { .skip-link {
position: absolute; position: absolute;
top: -40px; top: -40px; /* Adjust as needed to hide the link off-screen */
/* Adjust as needed to hide the link off-screen */
left: 0; left: 0;
background-color: #fff; background-color: #fff; /* Background color to cover the link for screen readers */
/* Background color to cover the link for screen readers */ z-index: 1060; /* Ensure it's above other content */
z-index: 1060;
/* Ensure it's above other content */
} }
.skip-link:focus { .skip-link:focus {
top: 0; top: 0; /* Bring the link back into view when it receives focus */
/* Bring the link back into view when it receives focus */
} }
#outer_container a:not(.btn):hover, footer a:not(.btn):hover { #outer_container a:not(.btn):hover, footer a:not(.btn):hover {
text-decoration: underline; text-decoration: underline;
} }
#outer_container a:not(.btn):has(span.fe):hover, footer a:not(.btn):has(span.fe):hover { #outer_container a:not(.btn):has(span.fe):hover, footer a:not(.btn):has(span.fe):hover { /* do not have underlines on icons */
/* do not have underlines on icons */
text-decoration: none; text-decoration: none;
} }
#outer_container a:not(.btn):active, footer a:not(.btn):active { #outer_container a:not(.btn):active, footer a:not(.btn):active {
@ -660,8 +654,7 @@ div.navbar {
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center center; background-position: center center;
background-size: cover; background-size: cover;
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0; /* top-left | top-right | bottom-right | bottom-left */
/* top-left | top-right | bottom-right | bottom-left */
height: 176px; height: 176px;
margin-left: -12px; margin-left: -12px;
margin-right: -12px; margin-right: -12px;
@ -878,8 +871,7 @@ div.navbar {
margin: 0; margin: 0;
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2; /* Limits the text to a specific number of lines */
/* Limits the text to a specific number of lines */
line-clamp: 2; line-clamp: 2;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -919,8 +911,7 @@ div.navbar {
} }
.post_teaser_video_preview .video-wrapper { .post_teaser_video_preview .video-wrapper {
position: relative; position: relative;
padding-bottom: 56.25%; padding-bottom: 56.25%; /* 16:9 aspect ratio */
/* 16:9 aspect ratio */
height: 0; height: 0;
overflow: hidden; overflow: hidden;
} }
@ -1022,8 +1013,7 @@ div.navbar {
#masonry .item img { #masonry .item img {
width: 100%; width: 100%;
display: block; display: block;
height: auto; height: auto; /* Ensure aspect ratio is maintained */
/* Ensure aspect ratio is maintained */
} }
#masonry .item .masonry_thumb a, #masonry .item .masonry_thumb a:active { #masonry .item .masonry_thumb a, #masonry .item .masonry_thumb a:active {
border: none; border: none;
@ -1195,8 +1185,7 @@ div.navbar {
display: inline-block; display: inline-block;
} }
.voting_buttons_new .upvote_button, .voting_buttons_new .downvote_button { .voting_buttons_new .upvote_button, .voting_buttons_new .downvote_button {
position: relative; position: relative; /* so the htmx-indicators can be position: absolute */
/* so the htmx-indicators can be position: absolute */
display: inline-block; display: inline-block;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
@ -1735,6 +1724,12 @@ h1 .warning_badge {
object-fit: cover; object-fit: cover;
} }
.community_icon_small {
width: 20px;
height: 20px;
margin-right: 2px;
}
.bump_up { .bump_up {
position: absolute; position: absolute;
top: 115px; top: 115px;
@ -1956,7 +1951,6 @@ form h5 {
--bs-link-color-rgb: black; --bs-link-color-rgb: black;
--bs-link-hover-color-rgb: #333; --bs-link-hover-color-rgb: #333;
} }
[data-bs-theme=dark] { [data-bs-theme=dark] {
--bs-body-bg: black; --bs-body-bg: black;
--bs-link-color: white; --bs-link-color: white;
@ -1966,84 +1960,65 @@ form h5 {
--bs-body-color: white; --bs-body-color: white;
--bs-body-color-rgb: white; --bs-body-color-rgb: white;
} }
.btn-primary { .btn-primary {
--bs-btn-bg: black; --bs-btn-bg: black;
--bs-btn-border-color: black; --bs-btn-border-color: black;
--bs-btn-hover-bg: #333; --bs-btn-hover-bg: #333;
} }
[data-bs-theme=dark] .btn-primary { [data-bs-theme=dark] .btn-primary {
--bs-btn-bg: white; --bs-btn-bg: white;
--bs-btn-border-color: white; --bs-btn-border-color: white;
--bs-btn-hover-bg: white; --bs-btn-hover-bg: white;
--bs-btn-color: black; --bs-btn-color: black;
} }
[data-bs-theme=dark] .btn-primary:hover { [data-bs-theme=dark] .btn-primary:hover {
--bs-btn-hover-color: black; --bs-btn-hover-color: black;
} }
.btn-outline-secondary { .btn-outline-secondary {
--bs-btn-color: black; --bs-btn-color: black;
} }
[data-bs-theme=dark] .btn-outline-secondary { [data-bs-theme=dark] .btn-outline-secondary {
--bs-btn-color: white; --bs-btn-color: white;
} }
.post_list .post_teaser { .post_list .post_teaser {
border-bottom: solid 1px black; border-bottom: solid 1px black;
} }
[data-bs-theme=dark] .post_list .post_teaser { [data-bs-theme=dark] .post_list .post_teaser {
border-bottom: solid 1px white; border-bottom: solid 1px white;
} }
.domain_link, .domain_link a { .domain_link, .domain_link a {
color: black; color: black;
} }
[data-bs-theme=dark] .domain_link, [data-bs-theme=dark] .domain_link a { [data-bs-theme=dark] .domain_link, [data-bs-theme=dark] .domain_link a {
color: ghostwhite; color: ghostwhite;
} }
.main_pane, .voting_buttons div { .main_pane, .voting_buttons div {
border: solid 1px #333; border: solid 1px #333;
} }
[data-bs-theme=dark] .main_pane, [data-bs-theme=dark] .voting_buttons div { [data-bs-theme=dark] .main_pane, [data-bs-theme=dark] .voting_buttons div {
border: solid 1px #eee; border: solid 1px #eee;
} }
div.navbar { div.navbar {
border-bottom: solid 1px #333; border-bottom: solid 1px #333;
} }
[data-bs-theme=dark] div.navbar { [data-bs-theme=dark] div.navbar {
border-bottom: solid 1px #fff; border-bottom: solid 1px #fff;
} }
.nav-link { .nav-link {
color: black; color: black;
} }
[data-bs-theme=dark] .nav-link { [data-bs-theme=dark] .nav-link {
color: white; color: white;
} }
.card { .card {
--bs-card-border-color: #333; --bs-card-border-color: #333;
} }
[data-bs-theme=dark] .card { [data-bs-theme=dark] .card {
--bs-card-border-color: #fff; --bs-card-border-color: #fff;
} }
.coolfieldset.collapsed legend, .coolfieldset legend, .coolfieldset.expanded legend { .coolfieldset.collapsed legend, .coolfieldset legend, .coolfieldset.expanded legend {
background-color: white; background-color: white;
} }
[data-bs-theme=dark] .coolfieldset.collapsed legend, [data-bs-theme=dark] .coolfieldset legend, [data-bs-theme=dark] .coolfieldset.expanded legend { [data-bs-theme=dark] .coolfieldset.collapsed legend, [data-bs-theme=dark] .coolfieldset legend, [data-bs-theme=dark] .coolfieldset.expanded legend {
background-color: black; background-color: black;
} }
@ -2054,19 +2029,16 @@ form h5 {
@media print { @media print {
#navbarSupportedContent, #side_pane, .voting_buttons, .comment_actions, footer, .post_reply_form, #post_replies .btn-group, #navbarSupportedContent, #side_pane, .voting_buttons, .comment_actions, footer, .post_reply_form, #post_replies .btn-group,
.navbar .nav-link, .post_utilities_bar, .community_header, .community_icon_big, .mobile_create_post { .navbar .nav-link, .post_utilities_bar, .community_header, .community_icon_big, .mobile_create_post {
display: none; display: none;
} }
.navbar { .navbar {
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
} }
.comments > .comment, .post_teaser { .comments > .comment, .post_teaser {
break-inside: avoid; break-inside: avoid;
} }
.main_pane { .main_pane {
border: none; border: none;
padding: 0; padding: 0;
@ -2074,16 +2046,13 @@ form h5 {
.main_pane > .btn-group { .main_pane > .btn-group {
display: none; display: none;
} }
.div.navbar { .div.navbar {
border-bottom: none; border-bottom: none;
} }
.limit_height { .limit_height {
overflow: visible !important; overflow: visible !important;
max-height: initial; max-height: initial;
} }
.comment .show-more { .comment .show-more {
display: none; display: none;
} }

View file

@ -1412,6 +1412,12 @@ h1 .warning_badge {
object-fit: cover; object-fit: cover;
} }
.community_icon_small {
width: 20px;
height: 20px;
margin-right: 2px;
}
.bump_up { .bump_up {
position: absolute; position: absolute;
top: 115px; top: 115px;

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 == '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 == '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 == '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 -%} {% 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> <li><a class="dropdown-item{% if active_child == 'read_posts' %} active{% endif %}" href="/read-posts">{{ _('Read Posts') }}</a></li>
{% endif -%} {% endif -%}

View file

@ -7,8 +7,10 @@
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">c/{{ post.community.name }}</a>{% endif -%} <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span> {% if post.community.icon_image() %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image() }}" alt="Community icon" />{% endif -%}
c/{{ post.community.name }}</a>{% endif -%}
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span>
{% if post.body_html -%} {% if post.body_html -%}
<div class="post_teaser_article_preview small"> <div class="post_teaser_article_preview small">
{{ first_paragraph(post.body_html) | safe }} {{ first_paragraph(post.body_html) | safe }}

View file

@ -10,8 +10,10 @@
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">c/{{ post.community.name }}</a>{% endif -%} <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span> {% if post.community.icon_image() %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image() }}" alt="Community icon" />{% endif -%}
c/{{ post.community.name }}</a>{% endif -%}
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span>
{% if post.image_id and not low_bandwidth -%} {% if post.image_id and not low_bandwidth -%}
<div class="post_teaser_image_preview"> <div class="post_teaser_image_preview">
<a href="{{ post.image.view_url() }}" rel="nofollow ugc" aria-label="{{ _('View image') }}" target="_blank"><img src="{{ post.image.medium_url() }}" <a href="{{ post.image.view_url() }}" rel="nofollow ugc" aria-label="{{ _('View image') }}" target="_blank"><img src="{{ post.image.medium_url() }}"

View file

@ -13,8 +13,10 @@
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">c/{{ post.community.name }}</a>{% endif -%} <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span> {% if post.community.icon_image() %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image() }}" alt="Community icon" />{% endif -%}
c/{{ post.community.name }}</a>{% endif -%}
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span>
{% if post.body_html -%} {% if post.body_html -%}
<div class="post_teaser_link_preview small"> <div class="post_teaser_link_preview small">
{{ first_paragraph(post.body_html) | safe }} {{ first_paragraph(post.body_html) | safe }}

View file

@ -8,8 +8,10 @@
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">c/{{ post.community.name }}</a>{% endif -%} <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span> {% if post.community.icon_image() %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image() }}" alt="Community icon" />{% endif -%}
c/{{ post.community.name }}</a>{% endif -%}
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span>
{% include "post/post_teaser/_utilities_bar.html" %} {% include "post/post_teaser/_utilities_bar.html" %}
</div> </div>
<div class="col col-2 col_thumbnail"> <div class="col col-2 col_thumbnail">

View file

@ -15,8 +15,10 @@
{% endif -%} {% endif -%}
{% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%} {% if post.sticky -%}<span class="fe fe-sticky-right"></span>{% endif -%}
</h3> </h3>
<span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">c/{{ post.community.name }}</a>{% endif -%} <span class="author small">{% if show_post_community -%}<a href="/c/{{ post.community.link() }}" aria-label="{{ _('Go to community %(name)s', name=post.community.name) }}">
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span> {% if post.community.icon_image() %}<img class="community_icon_small rounded-circle" src="{{ post.community.icon_image() }}" alt="Community icon" />{% endif -%}
c/{{ post.community.name }}</a>{% endif -%}
by {{ render_username(post.author) }} <time datetime="{{ post.last_active }}">{{ post.posted_at_localized(sort, locale) }}</time></span>
{% if not low_bandwidth %} {% if not low_bandwidth %}
<div class="post_teaser_video_preview"> <div class="post_teaser_video_preview">
<div class="max_width_512"> <div class="max_width_512">

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.activitypub.util import find_actor_or_create
from app.auth.util import random_token from app.auth.util import random_token
from app.community.util import save_icon_file, save_banner_file, retrieve_mods_and_backfill 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, \ from app.constants import *
POST_TYPE_IMAGE, POST_TYPE_POLL
from app.email import send_verification_email from app.email import send_verification_email
from app.models import Post, Community, CommunityMember, User, PostReply, PostVote, Notification, utcnow, File, Site, \ from app.models import Post, Community, CommunityMember, User, PostReply, PostVote, Notification, utcnow, File, Site, \
Instance, Report, UserBlock, CommunityBan, CommunityJoinRequest, CommunityBlock, Filter, Domain, DomainBlock, \ 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 import bp
from app.user.forms import ProfileForm, SettingsForm, DeleteAccountForm, ReportUserForm, \ from app.user.forms import ProfileForm, SettingsForm, DeleteAccountForm, ReportUserForm, \
FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm
@ -1143,6 +1142,86 @@ def user_bookmarks_comments():
next_url=next_url, prev_url=prev_url) 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']) @bp.route('/u/<actor>/fediverse_redirect', methods=['GET', 'POST'])
def fediverse_redirect(actor): def fediverse_redirect(actor):
actor = actor.strip() actor = actor.strip()