mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
refactor home page based on #293
This commit is contained in:
parent
e12838cf9f
commit
56a4d60aab
11 changed files with 99 additions and 116 deletions
|
@ -179,6 +179,7 @@ def register(app):
|
||||||
'cut_off': cut_off,
|
'cut_off': cut_off,
|
||||||
'community_id': community.id
|
'community_id': community.id
|
||||||
})
|
})
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
# Remove activity older than 3 days
|
# Remove activity older than 3 days
|
||||||
db.session.query(ActivityPubLog).filter(ActivityPubLog.created_at < utcnow() - timedelta(days=3)).delete()
|
db.session.query(ActivityPubLog).filter(ActivityPubLog.created_at < utcnow() - timedelta(days=3)).delete()
|
||||||
|
@ -196,6 +197,7 @@ def register(app):
|
||||||
PostReply.posted_at < utcnow() - timedelta(days=7)).all():
|
PostReply.posted_at < utcnow() - timedelta(days=7)).all():
|
||||||
post_reply.delete_dependencies()
|
post_reply.delete_dependencies()
|
||||||
db.session.delete(post_reply)
|
db.session.delete(post_reply)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
for post in Post.query.filter(Post.deleted == True,
|
for post in Post.query.filter(Post.deleted == True,
|
||||||
Post.posted_at < utcnow() - timedelta(days=7)).all():
|
Post.posted_at < utcnow() - timedelta(days=7)).all():
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import os.path
|
import os.path
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from math import log
|
|
||||||
from random import randint
|
from random import randint
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask_caching import CachedResponse
|
from flask_caching import CachedResponse
|
||||||
|
@ -13,22 +11,22 @@ from app.activitypub.util import users_total, active_month, local_posts, local_c
|
||||||
from app.activitypub.signature import default_context
|
from app.activitypub.signature import default_context
|
||||||
from app.constants import SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER, POST_TYPE_IMAGE, POST_TYPE_LINK, \
|
from app.constants import SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER, POST_TYPE_IMAGE, POST_TYPE_LINK, \
|
||||||
SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_VIDEO, POST_TYPE_POLL
|
SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR, POST_TYPE_VIDEO, POST_TYPE_POLL
|
||||||
from app.email import send_email, send_welcome_email
|
from app.email import send_email
|
||||||
from app.inoculation import inoculation
|
from app.inoculation import inoculation
|
||||||
from app.main import bp
|
from app.main import bp
|
||||||
from flask import g, session, flash, request, current_app, url_for, redirect, make_response, jsonify
|
from flask import g, session, flash, request, current_app, url_for, redirect, make_response, jsonify
|
||||||
from flask_moment import moment
|
from flask_moment import moment
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from flask_babel import _, get_locale
|
from flask_babel import _, get_locale
|
||||||
from sqlalchemy import select, desc, text
|
from sqlalchemy import desc, text
|
||||||
from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \
|
from app.utils import render_template, get_setting, request_etag_matches, return_304, blocked_domains, \
|
||||||
ap_datetime, ip_address, retrieve_block_list, shorten_string, markdown_to_text, user_filters_home, \
|
ap_datetime, shorten_string, markdown_to_text, user_filters_home, \
|
||||||
joined_communities, moderating_communities, parse_page, theme_list, get_request, markdown_to_html, allowlist_html, \
|
joined_communities, moderating_communities, markdown_to_html, allowlist_html, \
|
||||||
blocked_instances, communities_banned_from, topic_tree, recently_upvoted_posts, recently_downvoted_posts, \
|
blocked_instances, communities_banned_from, topic_tree, recently_upvoted_posts, recently_downvoted_posts, \
|
||||||
generate_image_from_video_url, blocked_users, microblog_content_to_title, menu_topics, languages_for_form, \
|
blocked_users, menu_topics, languages_for_form, \
|
||||||
make_cache_key, blocked_communities
|
make_cache_key, blocked_communities
|
||||||
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic, File, Instance, \
|
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic, Instance, \
|
||||||
InstanceRole, Notification, Language, community_language, PostReply, ModLog
|
Notification, Language, community_language, ModLog
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/', methods=['HEAD', 'GET', 'POST'])
|
@bp.route('/', methods=['HEAD', 'GET', 'POST'])
|
||||||
|
@ -41,57 +39,25 @@ def index(sort=None, view_filter=None):
|
||||||
'Accept', ''):
|
'Accept', ''):
|
||||||
return activitypub_application()
|
return activitypub_application()
|
||||||
|
|
||||||
if 'view_filter' in request.view_args:
|
|
||||||
view_filter = request.view_args['view_filter']
|
|
||||||
|
|
||||||
|
|
||||||
return CachedResponse(
|
return CachedResponse(
|
||||||
response=home_page('home', sort, view_filter),
|
response=home_page(sort, view_filter),
|
||||||
timeout=50 if current_user.is_anonymous else 5,
|
timeout=50 if current_user.is_anonymous else 5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/popular', methods=['GET'])
|
def home_page(sort, view_filter):
|
||||||
@bp.route('/popular/<sort>', methods=['GET'])
|
|
||||||
@bp.route('/popular/<sort>/<view_filter>', methods=['GET', 'POST'])
|
|
||||||
@cache.cached(timeout=5, make_cache_key=make_cache_key)
|
|
||||||
def popular(sort=None, view_filter=None):
|
|
||||||
|
|
||||||
if 'view_filter' in request.view_args:
|
|
||||||
view_filter = request.view_args['view_filter']
|
|
||||||
|
|
||||||
return CachedResponse(
|
|
||||||
response=home_page('popular', sort, view_filter),
|
|
||||||
timeout=50 if current_user.is_anonymous else 5,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/all', methods=['GET'])
|
|
||||||
@bp.route('/all/<sort>', methods=['GET'])
|
|
||||||
@bp.route('/all/<sort>/<view_filter>', methods=['GET', 'POST'])
|
|
||||||
@cache.cached(timeout=5, make_cache_key=make_cache_key)
|
|
||||||
def all_posts(sort=None, view_filter=None):
|
|
||||||
|
|
||||||
if 'view_filter' in request.view_args:
|
|
||||||
view_filter = request.view_args['view_filter']
|
|
||||||
|
|
||||||
return CachedResponse(
|
|
||||||
response=home_page('all', sort, view_filter),
|
|
||||||
timeout=50 if current_user.is_anonymous else 5,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def home_page(type, sort, view_filter):
|
|
||||||
verification_warning()
|
verification_warning()
|
||||||
|
|
||||||
if sort is None:
|
if sort is None:
|
||||||
sort = current_user.default_sort if current_user.is_authenticated else 'hot'
|
sort = current_user.default_sort if current_user.is_authenticated else 'hot'
|
||||||
|
|
||||||
if view_filter is None:
|
if view_filter is None:
|
||||||
view_filter = 'all'
|
view_filter = current_user.default_filter if current_user.is_authenticated else 'popular'
|
||||||
|
if view_filter is None:
|
||||||
|
view_filter = 'subscribed'
|
||||||
|
|
||||||
# If nothing has changed since their last visit, return HTTP 304
|
# If nothing has changed since their last visit, return HTTP 304
|
||||||
current_etag = f"{type}_{sort}_{hash(str(g.site.last_active))}"
|
current_etag = f"{sort}_{view_filter}_{hash(str(g.site.last_active))}"
|
||||||
if current_user.is_anonymous and request_etag_matches(current_etag):
|
if current_user.is_anonymous and request_etag_matches(current_etag):
|
||||||
return return_304(current_etag)
|
return return_304(current_etag)
|
||||||
|
|
||||||
|
@ -101,28 +67,9 @@ def home_page(type, sort, view_filter):
|
||||||
if current_user.is_anonymous:
|
if current_user.is_anonymous:
|
||||||
flash(_('Create an account to tailor this feed to your interests.'))
|
flash(_('Create an account to tailor this feed to your interests.'))
|
||||||
posts = Post.query.filter(Post.from_bot == False, Post.nsfw == False, Post.nsfl == False, Post.deleted == False)
|
posts = Post.query.filter(Post.from_bot == False, Post.nsfw == False, Post.nsfl == False, Post.deleted == False)
|
||||||
posts = posts.join(Community, Community.id == Post.community_id)
|
|
||||||
if type == 'home':
|
|
||||||
posts = posts.filter(Community.show_home == True)
|
|
||||||
elif type == 'popular':
|
|
||||||
posts = posts.filter(Community.show_popular == True).filter(Post.score > 100)
|
|
||||||
elif type == 'all':
|
|
||||||
posts = posts.filter(Community.show_all == True)
|
|
||||||
content_filters = {}
|
content_filters = {}
|
||||||
else:
|
else:
|
||||||
if type == 'home':
|
posts = Post.query.filter(Post.deleted == False)
|
||||||
posts = Post.query.join(CommunityMember, Post.community_id == CommunityMember.community_id).filter(
|
|
||||||
CommunityMember.is_banned == False, Post.deleted == False)
|
|
||||||
# posts = posts.join(User, CommunityMember.user_id == User.id).filter(User.id == current_user.id)
|
|
||||||
posts = posts.filter(CommunityMember.user_id == current_user.id)
|
|
||||||
elif type == 'popular':
|
|
||||||
posts = Post.query.filter(Post.from_bot == False)
|
|
||||||
posts = posts.join(Community, Community.id == Post.community_id)
|
|
||||||
posts = posts.filter(Community.show_popular == True, Post.score > 100, Post.deleted == False)
|
|
||||||
elif type == 'all':
|
|
||||||
posts = Post.query
|
|
||||||
posts = posts.join(Community, Community.id == Post.community_id)
|
|
||||||
posts = posts.filter(Community.show_all == True, Post.deleted == False)
|
|
||||||
|
|
||||||
if current_user.ignore_bots == 1:
|
if current_user.ignore_bots == 1:
|
||||||
posts = posts.filter(Post.from_bot == False)
|
posts = posts.filter(Post.from_bot == False)
|
||||||
|
@ -148,9 +95,16 @@ def home_page(type, sort, view_filter):
|
||||||
|
|
||||||
# view filter - subscribed/local/all
|
# view filter - subscribed/local/all
|
||||||
if view_filter == 'subscribed':
|
if view_filter == 'subscribed':
|
||||||
|
posts = posts.join(CommunityMember, Post.community_id == CommunityMember.community_id).filter(CommunityMember.is_banned == False)
|
||||||
posts = posts.filter(CommunityMember.user_id == current_user.id)
|
posts = posts.filter(CommunityMember.user_id == current_user.id)
|
||||||
elif view_filter == 'local':
|
elif view_filter == 'local':
|
||||||
posts = posts.filter(Post.instance_id == 1)
|
posts = posts.filter(Post.instance_id == 1)
|
||||||
|
elif view_filter == 'popular':
|
||||||
|
posts = posts.join(Community, Community.id == Post.community_id)
|
||||||
|
posts = posts.filter(Community.show_popular == True, Post.score > 100)
|
||||||
|
elif view_filter == 'all':
|
||||||
|
posts = posts.join(Community, Community.id == Post.community_id)
|
||||||
|
posts = posts.filter(Community.show_all == True)
|
||||||
|
|
||||||
# Sorting
|
# Sorting
|
||||||
if sort == 'hot':
|
if sort == 'hot':
|
||||||
|
@ -164,15 +118,8 @@ def home_page(type, sort, view_filter):
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
posts = posts.paginate(page=page, per_page=100 if current_user.is_authenticated and not low_bandwidth else 50, error_out=False)
|
posts = posts.paginate(page=page, per_page=100 if current_user.is_authenticated and not low_bandwidth else 50, error_out=False)
|
||||||
if type == 'home':
|
next_url = url_for('main.index', page=posts.next_num, sort=sort, view_filter=view_filter) if posts.has_next else None
|
||||||
next_url = url_for('main.index', page=posts.next_num, sort=sort) if posts.has_next else None
|
prev_url = url_for('main.index', page=posts.prev_num, sort=sort, view_filter=view_filter) if posts.has_prev and page != 1 else None
|
||||||
prev_url = url_for('main.index', page=posts.prev_num, sort=sort) if posts.has_prev and page != 1 else None
|
|
||||||
elif type == 'popular':
|
|
||||||
next_url = url_for('main.popular', page=posts.next_num, sort=sort) if posts.has_next else None
|
|
||||||
prev_url = url_for('main.popular', page=posts.prev_num, sort=sort) if posts.has_prev and page != 1 else None
|
|
||||||
elif type == 'all':
|
|
||||||
next_url = url_for('main.all_posts', page=posts.next_num, sort=sort) if posts.has_next else None
|
|
||||||
prev_url = url_for('main.all_posts', page=posts.prev_num, sort=sort) if posts.has_prev and page != 1 else None
|
|
||||||
|
|
||||||
# Active Communities
|
# Active Communities
|
||||||
active_communities = Community.query.filter_by(banned=False)
|
active_communities = Community.query.filter_by(banned=False)
|
||||||
|
@ -193,18 +140,17 @@ def home_page(type, sort, view_filter):
|
||||||
recently_upvoted = []
|
recently_upvoted = []
|
||||||
recently_downvoted = []
|
recently_downvoted = []
|
||||||
|
|
||||||
|
|
||||||
return render_template('index.html', posts=posts, active_communities=active_communities, show_post_community=True,
|
return render_template('index.html', posts=posts, active_communities=active_communities, show_post_community=True,
|
||||||
POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK, POST_TYPE_VIDEO=POST_TYPE_VIDEO, POST_TYPE_POLL=POST_TYPE_POLL,
|
POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK, POST_TYPE_VIDEO=POST_TYPE_VIDEO, POST_TYPE_POLL=POST_TYPE_POLL,
|
||||||
low_bandwidth=low_bandwidth, recently_upvoted=recently_upvoted,
|
low_bandwidth=low_bandwidth, recently_upvoted=recently_upvoted,
|
||||||
recently_downvoted=recently_downvoted,
|
recently_downvoted=recently_downvoted,
|
||||||
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
||||||
etag=f"{type}_{sort}_{hash(str(g.site.last_active))}", next_url=next_url, prev_url=prev_url,
|
etag=f"{sort}_{view_filter}_{hash(str(g.site.last_active))}", next_url=next_url, prev_url=prev_url,
|
||||||
#rss_feed=f"https://{current_app.config['SERVER_NAME']}/feed",
|
#rss_feed=f"https://{current_app.config['SERVER_NAME']}/feed",
|
||||||
#rss_feed_name=f"Posts on " + g.site.name,
|
#rss_feed_name=f"Posts on " + g.site.name,
|
||||||
title=f"{g.site.name} - {g.site.description}",
|
title=f"{g.site.name} - {g.site.description}",
|
||||||
description=shorten_string(markdown_to_text(g.site.sidebar), 150),
|
description=shorten_string(markdown_to_text(g.site.sidebar), 150),
|
||||||
content_filters=content_filters, type=type, sort=sort, view_filter=view_filter,
|
content_filters=content_filters, sort=sort, view_filter=view_filter,
|
||||||
announcement=allowlist_html(get_setting('announcement', '')),
|
announcement=allowlist_html(get_setting('announcement', '')),
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id()),
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
|
|
@ -621,6 +621,7 @@ class User(UserMixin, db.Model):
|
||||||
instance_id = db.Column(db.Integer, db.ForeignKey('instance.id'), index=True)
|
instance_id = db.Column(db.Integer, db.ForeignKey('instance.id'), index=True)
|
||||||
reports = db.Column(db.Integer, default=0) # how many times this user has been reported.
|
reports = db.Column(db.Integer, default=0) # how many times this user has been reported.
|
||||||
default_sort = db.Column(db.String(25), default='hot')
|
default_sort = db.Column(db.String(25), default='hot')
|
||||||
|
default_filter = db.Column(db.String(25), default='subscribed')
|
||||||
theme = db.Column(db.String(20), default='')
|
theme = db.Column(db.String(20), default='')
|
||||||
referrer = db.Column(db.String(256))
|
referrer = db.Column(db.String(256))
|
||||||
markdown_editor = db.Column(db.Boolean, default=False)
|
markdown_editor = db.Column(db.Boolean, default=False)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<div class="btn-group mt-1 mb-2">
|
<div class="btn-group mt-1 mb-2" aria-label="{{ _('Sorting methods: ') }}">
|
||||||
<a href="/{{ type }}/hot/{{ view_filter }}" class="btn {{ 'btn-primary' if sort == 'hot' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
<a href="/home/hot/{{ view_filter }}" class="btn {{ 'btn-primary' if sort == 'hot' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Trending now') }}">
|
||||||
{{ _('Hot') }}
|
{{ _('Hot') }}
|
||||||
</a>
|
</a>
|
||||||
<a href="/{{ type }}/top/{{ view_filter }}" class="btn {{ 'btn-primary' if sort == 'top' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
<a href="/home/top/{{ view_filter }}" class="btn {{ 'btn-primary' if sort == 'top' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Most upvotes in the last 24h') }}">
|
||||||
{{ _('Top') }}
|
{{ _('Top') }}
|
||||||
</a>
|
</a>
|
||||||
<a href="/{{ type }}/new/{{ view_filter }}" class="btn {{ 'btn-primary' if sort == 'new' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
<a href="/home/new/{{ view_filter }}" class="btn {{ 'btn-primary' if sort == 'new' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Latest posts') }}">
|
||||||
{{ _('New') }}
|
{{ _('New') }}
|
||||||
</a>
|
</a>
|
||||||
<a href="/{{ type }}/active/{{ view_filter }}" class="btn {{ 'btn-primary' if sort == 'active' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
<a href="/home/active/{{ view_filter }}" class="btn {{ 'btn-primary' if sort == 'active' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Recently commented on') }}">
|
||||||
{{ _('Active') }}
|
{{ _('Active') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
|
@ -1,13 +1,16 @@
|
||||||
<div class="btn-group mt-1 mb-2" style="float: right;">
|
<div class="btn-group mt-1 mb-2" style="float: right;" aria-label="{{ _('Post filters: ') }}">
|
||||||
{% if not current_user.is_anonymous %}
|
{% if not current_user.is_anonymous %}
|
||||||
<a href="/{{ type }}/{{ sort }}/subscribed" class="btn {{ 'btn-primary' if view_filter == 'subscribed' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
<a href="/home/{{ sort }}/subscribed" class="btn {{ 'btn-primary' if view_filter == 'subscribed' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Posts from joined communities') }}">
|
||||||
{{ _('Subscribed') }}
|
{{ _('Subscribed') }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/{{ type }}/{{ sort }}/local" class="btn {{ 'btn-primary' if view_filter == 'local' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
<a href="/home/{{ sort }}/local" class="btn {{ 'btn-primary' if view_filter == 'local' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Posts on this instance') }}">
|
||||||
{{ _('Local') }}
|
{{ _('Local') }}
|
||||||
</a>
|
</a>
|
||||||
<a href="/{{ type }}/{{ sort }}/all" class="btn {{ 'btn-primary' if view_filter == 'all' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
<a href="/home/{{ sort }}/popular" class="btn {{ 'btn-primary' if view_filter == 'popular' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('Posts from popular communities') }}">
|
||||||
|
{{ _('Popular') }}
|
||||||
|
</a>
|
||||||
|
<a href="/home/{{ sort }}/all" class="btn {{ 'btn-primary' if view_filter == 'all' else 'btn-outline-secondary' }}" rel="nofollow noindex" title="{{ _('No filter') }}">
|
||||||
{{ _('All') }}
|
{{ _('All') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
|
@ -118,13 +118,7 @@
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent" role="navigation">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent" role="navigation">
|
||||||
<ul class="nav navbar-nav ml-md-4">
|
<ul class="nav navbar-nav ml-md-4">
|
||||||
{% if current_user.is_anonymous %}
|
{% if current_user.is_anonymous %}
|
||||||
<li class="nav-item dropdown{% if active_parent == 'home' %} active{% endif %}">
|
<li class="nav-item"><a class="nav-link" href="/">{{ _('Home') }}</a></li>
|
||||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="/home" aria-haspopup="true" aria-expanded="false">{{ _('Home') }}</a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li><a class="dropdown-item{% if active_child == 'popular' %} active{% endif %}" href="/home"><span class="fe fe-home"></span>{{ _('Home') }}</a></li>
|
|
||||||
<li><a class="dropdown-item{% if active_child == 'popular' %} active{% endif %}" href="/popular"><span class="fe fe-popular"></span>{{ _('Popular') }}</a></li>
|
|
||||||
<li><a class="dropdown-item{% if active_child == 'all_posts' %} active{% endif %}" href="/all"><span class="fe fe-all"></span>{{ _('All posts') }}</a></li>
|
|
||||||
</ul>
|
|
||||||
<li class="nav-item dropdown{% if active_parent == 'communities' %} active{% endif %}">
|
<li class="nav-item dropdown{% if active_parent == 'communities' %} active{% endif %}">
|
||||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="/topics" aria-haspopup="true" aria-expanded="false">{{ _('Topics') }}</a>
|
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="/topics" aria-haspopup="true" aria-expanded="false">{{ _('Topics') }}</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
@ -144,13 +138,7 @@
|
||||||
<li class="nav-item"><a class="nav-link" href="/auth/register">{{ _('Register') }}</a></li>
|
<li class="nav-item"><a class="nav-link" href="/auth/register">{{ _('Register') }}</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="/donate">{{ _('Donate') }}</a></li>
|
<li class="nav-item"><a class="nav-link" href="/donate">{{ _('Donate') }}</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item dropdown{% if active_parent == 'home' %} active{% endif %}">
|
<li class="nav-item"><a class="nav-link" href="/">{{ _('Home') }}</a></li>
|
||||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="/home" aria-haspopup="true" aria-expanded="false">{{ _('Home') }}</a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li><a class="dropdown-item{% if active_child == 'home' %} active{% endif %}" href="/home"><span class="fe fe-home"></span>{{ _('Home') }}</a></li>
|
|
||||||
<li><a class="dropdown-item{% if active_child == 'popular' %} active{% endif %}" href="/popular"><span class="fe fe-popular"></span>{{ _('Popular') }}</a></li>
|
|
||||||
<li><a class="dropdown-item{% if active_child == 'all' %} active{% endif %}" href="/all"><span class="fe fe-all"></span>{{ _('All posts') }}</a></li>
|
|
||||||
</ul>
|
|
||||||
<li class="nav-item dropdown{% if active_parent == 'communities' %} active{% endif %}">
|
<li class="nav-item dropdown{% if active_parent == 'communities' %} active{% endif %}">
|
||||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="/topics" aria-haspopup="true" aria-expanded="false">{{ _('Topics') }}</a>
|
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="/topics" aria-haspopup="true" aria-expanded="false">{{ _('Topics') }}</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% from 'bootstrap5/form.html' import render_form %}
|
{% from 'bootstrap5/form.html' import render_form %}
|
||||||
{% set active_child = type %}
|
{% set active_child = 'home' %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
{{ render_field(form.interface_language) }}
|
{{ render_field(form.interface_language) }}
|
||||||
{{ render_field(form.markdown_editor) }}
|
{{ render_field(form.markdown_editor) }}
|
||||||
{{ render_field(form.default_sort) }}
|
{{ render_field(form.default_sort) }}
|
||||||
|
{{ render_field(form.default_filter) }}
|
||||||
{{ render_field(form.theme) }}
|
{{ render_field(form.theme) }}
|
||||||
<h5>Import</h5>
|
<h5>Import</h5>
|
||||||
{{ render_field(form.import_file) }}
|
{{ render_field(form.import_file) }}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
from flask import session
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SubmitField, PasswordField, BooleanField, EmailField, TextAreaField, FileField, \
|
from wtforms import StringField, SubmitField, PasswordField, BooleanField, EmailField, TextAreaField, FileField, \
|
||||||
RadioField, DateField, SelectField, IntegerField
|
RadioField, DateField, SelectField, IntegerField
|
||||||
from wtforms.fields.choices import SelectMultipleField
|
|
||||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
||||||
from flask_babel import _, lazy_gettext as _l
|
from flask_babel import _, lazy_gettext as _l
|
||||||
|
|
||||||
|
@ -16,7 +14,8 @@ class ProfileForm(FlaskForm):
|
||||||
password_field = PasswordField(_l('Set new password'), validators=[Optional(), Length(min=1, max=50)],
|
password_field = PasswordField(_l('Set new password'), validators=[Optional(), Length(min=1, max=50)],
|
||||||
render_kw={"autocomplete": 'new-password'})
|
render_kw={"autocomplete": 'new-password'})
|
||||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)], render_kw={'rows': 5})
|
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)], render_kw={'rows': 5})
|
||||||
matrixuserid = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)], render_kw={'autocomplete': 'off'})
|
matrixuserid = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)],
|
||||||
|
render_kw={'autocomplete': 'off'})
|
||||||
profile_file = FileField(_l('Avatar image'), render_kw={'accept': 'image/*'})
|
profile_file = FileField(_l('Avatar image'), render_kw={'accept': 'image/*'})
|
||||||
banner_file = FileField(_l('Top banner image'), render_kw={'accept': 'image/*'})
|
banner_file = FileField(_l('Top banner image'), render_kw={'accept': 'image/*'})
|
||||||
bot = BooleanField(_l('This profile is a bot'))
|
bot = BooleanField(_l('This profile is a bot'))
|
||||||
|
@ -32,7 +31,8 @@ class ProfileForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class SettingsForm(FlaskForm):
|
class SettingsForm(FlaskForm):
|
||||||
interface_language = SelectField(_l('Interface language'), coerce=str, validators=[Optional()], render_kw={'class': 'form-select'})
|
interface_language = SelectField(_l('Interface language'), coerce=str, validators=[Optional()],
|
||||||
|
render_kw={'class': 'form-select'})
|
||||||
newsletter = BooleanField(_l('Subscribe to email newsletter'))
|
newsletter = BooleanField(_l('Subscribe to email newsletter'))
|
||||||
email_unread = BooleanField(_l('Receive email about missed notifications'))
|
email_unread = BooleanField(_l('Receive email about missed notifications'))
|
||||||
ignore_bots = BooleanField(_l('Hide posts by bots'))
|
ignore_bots = BooleanField(_l('Hide posts by bots'))
|
||||||
|
@ -46,11 +46,19 @@ class SettingsForm(FlaskForm):
|
||||||
manually_approves_followers = BooleanField(_l('Manually approve followers'))
|
manually_approves_followers = BooleanField(_l('Manually approve followers'))
|
||||||
import_file = FileField(_l('Import community subscriptions and user blocks from Lemmy'))
|
import_file = FileField(_l('Import community subscriptions and user blocks from Lemmy'))
|
||||||
sorts = [('hot', _l('Hot')),
|
sorts = [('hot', _l('Hot')),
|
||||||
('top', _l('Top')),
|
('top', _l('Top')),
|
||||||
('new', _l('New')),
|
('new', _l('New')),
|
||||||
('active', _l('Active')),
|
('active', _l('Active')),
|
||||||
]
|
]
|
||||||
default_sort = SelectField(_l('By default, sort posts by'), choices=sorts, validators=[DataRequired()], coerce=str, render_kw={'class': 'form-select'})
|
default_sort = SelectField(_l('Default post sort'), choices=sorts, validators=[DataRequired()], coerce=str,
|
||||||
|
render_kw={'class': 'form-select'})
|
||||||
|
filters = [('subscribed', _l('Subscribed')),
|
||||||
|
('local', _l('Local')),
|
||||||
|
('popular', _l('Popular')),
|
||||||
|
('all', _l('All')),
|
||||||
|
]
|
||||||
|
default_filter = SelectField(_l('Default home filter'), choices=filters, validators=[DataRequired()], coerce=str,
|
||||||
|
render_kw={'class': 'form-select'})
|
||||||
theme = SelectField(_l('Theme'), coerce=str, render_kw={'class': 'form-select'})
|
theme = SelectField(_l('Theme'), coerce=str, render_kw={'class': 'form-select'})
|
||||||
submit = SubmitField(_l('Save settings'))
|
submit = SubmitField(_l('Save settings'))
|
||||||
|
|
||||||
|
@ -113,8 +121,8 @@ class KeywordFilterEditForm(FlaskForm):
|
||||||
hide_type_choices = [(0, _l('Make semi-transparent')), (1, _l('Hide completely'))]
|
hide_type_choices = [(0, _l('Make semi-transparent')), (1, _l('Hide completely'))]
|
||||||
hide_type = RadioField(_l('Action to take'), choices=hide_type_choices, default=1, coerce=int)
|
hide_type = RadioField(_l('Action to take'), choices=hide_type_choices, default=1, coerce=int)
|
||||||
keywords = TextAreaField(_l('Keywords that trigger this filter'),
|
keywords = TextAreaField(_l('Keywords that trigger this filter'),
|
||||||
render_kw={'placeholder': 'One keyword or phrase per line', 'rows': 3},
|
render_kw={'placeholder': 'One keyword or phrase per line', 'rows': 3},
|
||||||
validators=[DataRequired(), Length(min=3, max=500)])
|
validators=[DataRequired(), Length(min=3, max=500)])
|
||||||
expire_after = DateField(_l('Expire after'), validators=[Optional()])
|
expire_after = DateField(_l('Expire after'), validators=[Optional()])
|
||||||
submit = SubmitField(_l('Save'))
|
submit = SubmitField(_l('Save'))
|
||||||
|
|
||||||
|
|
|
@ -232,6 +232,7 @@ def change_settings():
|
||||||
current_user.searchable = form.searchable.data
|
current_user.searchable = form.searchable.data
|
||||||
current_user.indexable = form.indexable.data
|
current_user.indexable = form.indexable.data
|
||||||
current_user.default_sort = form.default_sort.data
|
current_user.default_sort = form.default_sort.data
|
||||||
|
current_user.default_filter = form.default_filter.data
|
||||||
current_user.theme = form.theme.data
|
current_user.theme = form.theme.data
|
||||||
current_user.email_unread = form.email_unread.data
|
current_user.email_unread = form.email_unread.data
|
||||||
current_user.markdown_editor = form.markdown_editor.data
|
current_user.markdown_editor = form.markdown_editor.data
|
||||||
|
@ -269,6 +270,7 @@ def change_settings():
|
||||||
form.searchable.data = current_user.searchable
|
form.searchable.data = current_user.searchable
|
||||||
form.indexable.data = current_user.indexable
|
form.indexable.data = current_user.indexable
|
||||||
form.default_sort.data = current_user.default_sort
|
form.default_sort.data = current_user.default_sort
|
||||||
|
form.default_filter.data = current_user.default_filter
|
||||||
form.theme.data = current_user.theme
|
form.theme.data = current_user.theme
|
||||||
form.markdown_editor.data = current_user.markdown_editor
|
form.markdown_editor.data = current_user.markdown_editor
|
||||||
form.interface_language.data = current_user.interface_language
|
form.interface_language.data = current_user.interface_language
|
||||||
|
|
32
migrations/versions/f1f38dabd541_default_filter.py
Normal file
32
migrations/versions/f1f38dabd541_default_filter.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
"""default filter
|
||||||
|
|
||||||
|
Revision ID: f1f38dabd541
|
||||||
|
Revises: f6d6bd92cf88
|
||||||
|
Create Date: 2024-08-16 13:37:02.854345
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'f1f38dabd541'
|
||||||
|
down_revision = 'f6d6bd92cf88'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('default_filter', sa.String(length=25), nullable=True))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('default_filter')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
Loading…
Reference in a new issue