refactor home page based on #293

This commit is contained in:
rimu 2024-08-16 13:42:29 +12:00
parent e12838cf9f
commit 56a4d60aab
11 changed files with 99 additions and 116 deletions

View file

@ -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():

View file

@ -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()),

View file

@ -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)

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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">

View file

@ -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) }}

View 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'))

View file

@ -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

View 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 ###