mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
sort posts by top, hot, new
This commit is contained in:
parent
acfe35d98d
commit
b431a79518
16 changed files with 298 additions and 30 deletions
|
@ -701,7 +701,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
|
|||
user.instance.ip_address = ip_address
|
||||
user.instance.dormant = False
|
||||
if 'community' in vars() and community is not None:
|
||||
if community.is_local() and request_json['type'] not in ['Announce', 'Follow', 'Accept']:
|
||||
if community.is_local() and request_json['type'] not in ['Announce', 'Follow', 'Accept', 'Undo']:
|
||||
announce_activity_to_followers(community, user, request_json)
|
||||
# community.flush_cache()
|
||||
if 'post' in vars() and post is not None:
|
||||
|
|
|
@ -20,7 +20,7 @@ from PIL import Image, ImageOps
|
|||
from io import BytesIO
|
||||
|
||||
from app.utils import get_request, allowlist_html, html_to_markdown, get_setting, ap_datetime, markdown_to_html, \
|
||||
is_image_url, domain_from_url, gibberish, ensure_directory_exists, markdown_to_text, head_request
|
||||
is_image_url, domain_from_url, gibberish, ensure_directory_exists, markdown_to_text, head_request, post_ranking
|
||||
|
||||
|
||||
def public_key():
|
||||
|
@ -729,6 +729,8 @@ def downvote_post(post, user):
|
|||
db.session.add(vote)
|
||||
else:
|
||||
pass # they have already downvoted this post
|
||||
post.ranking = post_ranking(post.score, post.posted_at)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def downvote_post_reply(comment, user):
|
||||
|
@ -831,6 +833,8 @@ def upvote_post(post, user):
|
|||
effect = 0
|
||||
post.author.reputation += effect
|
||||
db.session.add(vote)
|
||||
post.ranking = post_ranking(post.score, post.posted_at)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id):
|
||||
|
@ -972,6 +976,7 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
|||
post.image = image
|
||||
if post is not None:
|
||||
db.session.add(post)
|
||||
post.ranking = post_ranking(post.score, post.posted_at)
|
||||
community.post_count += 1
|
||||
community.last_active = utcnow()
|
||||
activity_log.result = 'success'
|
||||
|
@ -986,7 +991,7 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
|
|||
db.session.add(vote)
|
||||
post.up_votes += 1
|
||||
post.score += 1
|
||||
post.ranking += 1
|
||||
post.ranking = post_ranking(post.score, post.posted_at)
|
||||
db.session.commit()
|
||||
return post
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ from app.utils import get_setting, render_template, allowlist_html, markdown_to_
|
|||
shorten_string, markdown_to_text, domain_from_url, validate_image, gibberish, community_membership, ap_datetime, \
|
||||
request_etag_matches, return_304, instance_banned, can_create, can_upvote, can_downvote
|
||||
from feedgen.feed import FeedGenerator
|
||||
from datetime import timezone
|
||||
from datetime import timezone, timedelta
|
||||
|
||||
|
||||
@bp.route('/add_local', methods=['GET', 'POST'])
|
||||
|
@ -98,6 +98,7 @@ def show_community(community: Community):
|
|||
abort(404)
|
||||
|
||||
page = request.args.get('page', 1, type=int)
|
||||
sort = request.args.get('sort', '')
|
||||
|
||||
mods = community.moderators()
|
||||
|
||||
|
@ -113,17 +114,24 @@ def show_community(community: Community):
|
|||
mod_list = User.query.filter(User.id.in_(mod_user_ids)).all()
|
||||
|
||||
if current_user.is_anonymous or current_user.ignore_bots:
|
||||
posts = community.posts.filter(Post.from_bot == False).order_by(desc(Post.last_active)).paginate(page=page, per_page=100, error_out=False)
|
||||
posts = community.posts.filter(Post.from_bot == False)
|
||||
else:
|
||||
posts = community.posts.order_by(desc(Post.last_active)).paginate(page=page, per_page=100, error_out=False)
|
||||
posts = community.posts
|
||||
if sort == '' or sort == 'hot':
|
||||
posts = posts.order_by(desc(Post.ranking))
|
||||
elif sort == 'top':
|
||||
posts = posts.filter(Post.posted_at > utcnow() - timedelta(days=7)).order_by(desc(Post.score))
|
||||
elif sort == 'new':
|
||||
posts = posts.order_by(desc(Post.posted_at))
|
||||
posts = posts.paginate(page=page, per_page=100, error_out=False)
|
||||
|
||||
description = shorten_string(community.description, 150) if community.description else None
|
||||
og_image = community.image.source_url if community.image_id else None
|
||||
|
||||
next_url = url_for('activitypub.community_profile', actor=community.ap_id if community.ap_id is not None else community.name,
|
||||
page=posts.next_num) if posts.has_next else None
|
||||
page=posts.next_num, sort=sort) if posts.has_next else None
|
||||
prev_url = url_for('activitypub.community_profile', actor=community.ap_id if community.ap_id is not None else community.name,
|
||||
page=posts.prev_num) if posts.has_prev and page != 1 else None
|
||||
page=posts.prev_num, sort=sort) if posts.has_prev and page != 1 else None
|
||||
|
||||
return render_template('community/community.html', community=community, title=community.title,
|
||||
is_moderator=is_moderator, is_owner=is_owner, is_admin=is_admin, mods=mod_list, posts=posts, description=description,
|
||||
|
|
|
@ -15,7 +15,7 @@ from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE
|
|||
from app.models import Community, File, BannedInstances, PostReply, PostVote, Post, utcnow, CommunityMember, Site, \
|
||||
Instance
|
||||
from app.utils import get_request, gibberish, markdown_to_html, domain_from_url, validate_image, allowlist_html, \
|
||||
html_to_markdown, is_image_url, ensure_directory_exists, inbox_domain
|
||||
html_to_markdown, is_image_url, ensure_directory_exists, inbox_domain, post_ranking
|
||||
from sqlalchemy import desc, text
|
||||
import os
|
||||
from opengraph_parse import parse_page
|
||||
|
@ -103,6 +103,7 @@ def retrieve_mods_and_backfill(community_id: int):
|
|||
post = post_json_to_model(activity['object']['object'], user, community)
|
||||
post.ap_create_id = activity['object']['id']
|
||||
post.ap_announce_id = activity['id']
|
||||
post.ranking = post_ranking(post.score, post.posted_at)
|
||||
db.session.commit()
|
||||
|
||||
activities_processed += 1
|
||||
|
@ -267,7 +268,9 @@ def save_post(form, post: Post):
|
|||
post.score = -1
|
||||
post.ranking = -1
|
||||
db.session.add(postvote)
|
||||
post.ranking = post_ranking(post.score, utcnow())
|
||||
db.session.add(post)
|
||||
|
||||
g.site.last_active = utcnow()
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from datetime import datetime, timedelta
|
||||
from math import log
|
||||
|
||||
from sqlalchemy.sql.operators import or_
|
||||
|
||||
from app import db, cache
|
||||
|
@ -12,7 +15,7 @@ from sqlalchemy import select, desc
|
|||
from sqlalchemy_searchable import search
|
||||
from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \
|
||||
ap_datetime, ip_address
|
||||
from app.models import Community, CommunityMember, Post, Site, User
|
||||
from app.models import Community, CommunityMember, Post, Site, User, utcnow
|
||||
|
||||
|
||||
@bp.route('/', methods=['HEAD', 'GET', 'POST'])
|
||||
|
@ -40,7 +43,7 @@ def index():
|
|||
if domains_ids:
|
||||
posts = posts.filter(or_(Post.domain_id.not_in(domains_ids), Post.domain_id == None))
|
||||
|
||||
posts = posts.order_by(desc(Post.last_active)).paginate(page=page, per_page=100, error_out=False)
|
||||
posts = posts.order_by(desc(Post.ranking)).paginate(page=page, per_page=100, error_out=False)
|
||||
|
||||
next_url = url_for('main.index', page=posts.next_num) if posts.has_next else None
|
||||
prev_url = url_for('main.index', page=posts.prev_num) if posts.has_prev and page != 1 else None
|
||||
|
@ -54,6 +57,78 @@ def index():
|
|||
rss_feed=f"https://{current_app.config['SERVER_NAME']}/feed", rss_feed_name=f"Posts on " + g.site.name)
|
||||
|
||||
|
||||
@bp.route('/new', methods=['HEAD', 'GET', 'POST'])
|
||||
def new_posts():
|
||||
verification_warning()
|
||||
|
||||
# If nothing has changed since their last visit, return HTTP 304
|
||||
current_etag = f"new_{hash(str(g.site.last_active))}"
|
||||
if current_user.is_anonymous and request_etag_matches(current_etag):
|
||||
return return_304(current_etag)
|
||||
|
||||
page = request.args.get('page', 1, type=int)
|
||||
|
||||
if current_user.is_anonymous:
|
||||
posts = Post.query.filter(Post.from_bot == False, Post.nsfw == False, Post.nsfl == False)
|
||||
else:
|
||||
posts = Post.query.join(CommunityMember, Post.community_id == CommunityMember.community_id).filter(
|
||||
CommunityMember.is_banned == False)
|
||||
posts = posts.join(User, CommunityMember.user_id == User.id).filter(User.id == current_user.id)
|
||||
domains_ids = blocked_domains(current_user.id)
|
||||
if domains_ids:
|
||||
posts = posts.filter(or_(Post.domain_id.not_in(domains_ids), Post.domain_id == None))
|
||||
|
||||
posts = posts.order_by(desc(Post.posted_at)).paginate(page=page, per_page=100, error_out=False)
|
||||
|
||||
next_url = url_for('main.new_posts', page=posts.next_num) if posts.has_next else None
|
||||
prev_url = url_for('main.new_posts', page=posts.prev_num) if posts.has_prev and page != 1 else None
|
||||
|
||||
active_communities = Community.query.filter_by(banned=False).order_by(desc(Community.last_active)).limit(5).all()
|
||||
|
||||
return render_template('new_posts.html', posts=posts, active_communities=active_communities,
|
||||
POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK,
|
||||
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
||||
etag=f"home_{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_name=f"Posts on " + g.site.name)
|
||||
|
||||
|
||||
@bp.route('/top', methods=['HEAD', 'GET', 'POST'])
|
||||
def top_posts():
|
||||
verification_warning()
|
||||
|
||||
# If nothing has changed since their last visit, return HTTP 304
|
||||
current_etag = f"best_{hash(str(g.site.last_active))}"
|
||||
if current_user.is_anonymous and request_etag_matches(current_etag):
|
||||
return return_304(current_etag)
|
||||
|
||||
page = request.args.get('page', 1, type=int)
|
||||
|
||||
if current_user.is_anonymous:
|
||||
posts = Post.query.filter(Post.from_bot == False, Post.nsfw == False, Post.nsfl == False)
|
||||
else:
|
||||
posts = Post.query.join(CommunityMember, Post.community_id == CommunityMember.community_id).filter(
|
||||
CommunityMember.is_banned == False)
|
||||
posts = posts.join(User, CommunityMember.user_id == User.id).filter(User.id == current_user.id)
|
||||
domains_ids = blocked_domains(current_user.id)
|
||||
if domains_ids:
|
||||
posts = posts.filter(or_(Post.domain_id.not_in(domains_ids), Post.domain_id == None))
|
||||
|
||||
posts = posts.filter(Post.posted_at > utcnow() - timedelta(days=1)).order_by(desc(Post.score)).paginate(page=page, per_page=100, error_out=False)
|
||||
|
||||
next_url = url_for('main.top_posts', page=posts.next_num) if posts.has_next else None
|
||||
prev_url = url_for('main.top_posts', page=posts.prev_num) if posts.has_prev and page != 1 else None
|
||||
|
||||
active_communities = Community.query.filter_by(banned=False).order_by(desc(Community.last_active)).limit(5).all()
|
||||
|
||||
return render_template('top_posts.html', posts=posts, active_communities=active_communities,
|
||||
POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK,
|
||||
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
||||
etag=f"home_{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_name=f"Posts on " + g.site.name)
|
||||
|
||||
|
||||
@bp.route('/communities', methods=['GET'])
|
||||
def list_communities():
|
||||
verification_warning()
|
||||
|
@ -99,10 +174,12 @@ def robots():
|
|||
|
||||
@bp.route('/test')
|
||||
def test():
|
||||
ip = request.headers.get('X-Forwarded-For') or request.remote_addr
|
||||
if ',' in ip: # Remove all but first ip addresses
|
||||
ip = ip[:ip.index(',')].strip()
|
||||
return ip
|
||||
return 'done'
|
||||
|
||||
#ip = request.headers.get('X-Forwarded-For') or request.remote_addr
|
||||
#if ',' in ip: # Remove all but first ip addresses
|
||||
# ip = ip[:ip.index(',')].strip()
|
||||
#return ip
|
||||
|
||||
|
||||
def verification_warning():
|
||||
|
|
|
@ -18,7 +18,7 @@ from app.models import Post, PostReply, \
|
|||
from app.post import bp
|
||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
||||
shorten_string, markdown_to_text, domain_from_url, validate_image, gibberish, ap_datetime, return_304, \
|
||||
request_etag_matches, ip_address, user_ip_banned, instance_banned, can_downvote, can_upvote
|
||||
request_etag_matches, ip_address, user_ip_banned, instance_banned, can_downvote, can_upvote, post_ranking
|
||||
|
||||
|
||||
def show_post(post_id: int):
|
||||
|
@ -155,11 +155,11 @@ def show_post(post_id: int):
|
|||
og_image = post.image.source_url if post.image_id else None
|
||||
description = shorten_string(markdown_to_text(post.body), 150) if post.body else None
|
||||
|
||||
return render_template('post/post.html', title=post.title, post=post, is_moderator=is_moderator,
|
||||
return render_template('post/post.html', title=post.title, post=post, is_moderator=is_moderator, community=post.community,
|
||||
canonical=post.ap_id, form=form, replies=replies, THREAD_CUTOFF_DEPTH=constants.THREAD_CUTOFF_DEPTH,
|
||||
description=description, og_image=og_image, POST_TYPE_IMAGE=constants.POST_TYPE_IMAGE,
|
||||
POST_TYPE_LINK=constants.POST_TYPE_LINK, POST_TYPE_ARTICLE=constants.POST_TYPE_ARTICLE,
|
||||
etag=f"{post.id}_{hash(post.last_active)}", markdown_editor=True, community=community)
|
||||
etag=f"{post.id}_{hash(post.last_active)}", markdown_editor=True)
|
||||
|
||||
|
||||
@bp.route('/post/<int:post_id>/<vote_direction>', methods=['GET', 'POST'])
|
||||
|
@ -247,11 +247,12 @@ def post_vote(post_id: int, vote_direction):
|
|||
current_user.last_seen = utcnow()
|
||||
current_user.ip_address = ip_address()
|
||||
if not current_user.banned:
|
||||
post.ranking = post_ranking(post.score, post.created_at)
|
||||
db.session.commit()
|
||||
current_user.recalculate_attitude()
|
||||
db.session.commit()
|
||||
post.flush_cache()
|
||||
return render_template('post/_post_voting_buttons.html', post=post,
|
||||
return render_template('post/_post_voting_buttons.html', post=post, community=post.community,
|
||||
upvoted_class=upvoted_class,
|
||||
downvoted_class=downvoted_class)
|
||||
|
||||
|
@ -327,7 +328,7 @@ def comment_vote(comment_id, vote_direction):
|
|||
comment.post.flush_cache()
|
||||
return render_template('post/_voting_buttons.html', comment=comment,
|
||||
upvoted_class=upvoted_class,
|
||||
downvoted_class=downvoted_class)
|
||||
downvoted_class=downvoted_class, community=comment.community)
|
||||
|
||||
|
||||
@bp.route('/post/<int:post_id>/comment/<int:comment_id>')
|
||||
|
|
11
app/templates/_home_nav.html
Normal file
11
app/templates/_home_nav.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class="btn-group mt-1 mb-2">
|
||||
<a href="/" class="btn {{ 'btn-primary' if request.path == '/' else 'btn-outline-secondary' }}" rel="nofollow">
|
||||
{{ _('Hot') }}
|
||||
</a>
|
||||
<a href="/top" class="btn {{ 'btn-primary' if request.path == '/top' else 'btn-outline-secondary' }}" rel="nofollow">
|
||||
{{ _('Top') }}
|
||||
</a>
|
||||
<a href="/new" class="btn {{ 'btn-primary' if request.path == '/new' else 'btn-outline-secondary' }}" rel="nofollow">
|
||||
{{ _('New') }}
|
||||
</a>
|
||||
</div>
|
|
@ -40,7 +40,7 @@
|
|||
</table>
|
||||
<nav aria-label="Pagination" class="mt-4">
|
||||
{% if prev_url %}
|
||||
<a href="{{ prev_url }}" class="btn btn-primary">
|
||||
<a href="{{ prev_url }}" class="btn btn-primary" rel="nofollow">
|
||||
<span aria-hidden="true">←</span> {{ _('Previous page') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
11
app/templates/community/_community_nav.html
Normal file
11
app/templates/community/_community_nav.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class="btn-group mt-1 mb-2">
|
||||
<a href="?sort=hot" class="btn {{ 'btn-primary' if request.args.get('sort', '') == '' or request.args.get('sort', '') == 'hot' else 'btn-outline-secondary' }}" rel="nofollow">
|
||||
{{ _('Hot') }}
|
||||
</a>
|
||||
<a href="?sort=top" class="btn {{ 'btn-primary' if request.args.get('sort', '') == 'top' else 'btn-outline-secondary' }}" rel="nofollow">
|
||||
{{ _('Top') }}
|
||||
</a>
|
||||
<a href="?sort=new" class="btn {{ 'btn-primary' if request.args.get('sort', '') == 'new' else 'btn-outline-secondary' }}" rel="nofollow">
|
||||
{{ _('New') }}
|
||||
</a>
|
||||
</div>
|
|
@ -35,6 +35,7 @@
|
|||
</nav>
|
||||
<h1 class="mt-2">{{ community.title }}</h1>
|
||||
{% endif %}
|
||||
{% include "community/_community_nav.html" %}
|
||||
<div class="post_list">
|
||||
{% for post in posts %}
|
||||
{% include 'post/_post_teaser.html' %}
|
||||
|
@ -45,12 +46,12 @@
|
|||
|
||||
<nav aria-label="Pagination" class="mt-4">
|
||||
{% if prev_url %}
|
||||
<a href="{{ prev_url }}" class="btn btn-primary">
|
||||
<a href="{{ prev_url }}" class="btn btn-primary" rel='nofollow'>
|
||||
<span aria-hidden="true">←</span> {{ _('Previous page') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if next_url %}
|
||||
<a href="{{ next_url }}" class="btn btn-primary">
|
||||
<a href="{{ next_url }}" class="btn btn-primary" rel='nofollow'>
|
||||
{{ _('Next page') }} <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{% block app_content %}
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 position-relative main_pane">
|
||||
<h1 class="mt-2">{{ _('Recent posts') }}</h1>
|
||||
{% include "_home_nav.html" %}
|
||||
<div class="post_list">
|
||||
{% for post in posts %}
|
||||
{% include 'post/_post_teaser.html' %}
|
||||
|
@ -15,12 +15,12 @@
|
|||
|
||||
<nav aria-label="Pagination" class="mt-4">
|
||||
{% if prev_url %}
|
||||
<a href="{{ prev_url }}" class="btn btn-primary">
|
||||
<a href="{{ prev_url }}" class="btn btn-primary" rel="nofollow">
|
||||
<span aria-hidden="true">←</span> {{ _('Previous page') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if next_url %}
|
||||
<a href="{{ next_url }}" class="btn btn-primary">
|
||||
<a href="{{ next_url }}" class="btn btn-primary" rel="nofollow">
|
||||
{{ _('Next page') }} <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -15,12 +15,9 @@
|
|||
{{ _('Subscribed') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
|
||||
<div class="btn-group">
|
||||
|
||||
<a href="{{ url_for('community.add_local') }}" class="btn btn-outline-secondary">{{ _('Create local') }}</a>
|
||||
<a href="{{ url_for('community.add_remote') }}" class="btn btn-outline-secondary">{{ _('Add remote') }}</a>
|
||||
</div>
|
||||
|
|
71
app/templates/new_posts.html
Normal file
71
app/templates/new_posts.html
Normal file
|
@ -0,0 +1,71 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'bootstrap5/form.html' import render_form %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 position-relative main_pane">
|
||||
{% include "_home_nav.html" %}
|
||||
<div class="post_list">
|
||||
{% for post in posts %}
|
||||
{% include 'post/_post_teaser.html' %}
|
||||
{% else %}
|
||||
<p>{{ _('No posts yet.') }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<nav aria-label="Pagination" class="mt-4">
|
||||
{% if prev_url %}
|
||||
<a href="{{ prev_url }}" class="btn btn-primary" rel="nofollow">
|
||||
<span aria-hidden="true">←</span> {{ _('Previous page') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if next_url %}
|
||||
<a href="{{ next_url }}" class="btn btn-primary" rel="nofollow">
|
||||
{{ _('Next page') }} <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="get">
|
||||
<input type="search" name="search" class="form-control mt-2" placeholder="{{ _('Search') }}" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('Active communities') }}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for community in active_communities %}
|
||||
<li class="list-group-item">
|
||||
<a href="/c/{{ community.link() }}"><img src="{{ community.icon_image() }}" class="community_icon rounded-circle" loading="lazy" />
|
||||
{{ community.display_name() }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="mt-4"><a class="btn btn-primary" href="/communities">Explore communities</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('About %(site_name)s', site_name=g.site.name) }}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>{{ g.site.description|safe }}</strong></p>
|
||||
<p>{{ g.site.sidebar|safe }}</p>
|
||||
<p class="mt-4">
|
||||
<a class="no-underline" href="{{ rss_feed }}" rel="nofollow"><span class="fe fe-rss"></span> RSS feed</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
71
app/templates/top_posts.html
Normal file
71
app/templates/top_posts.html
Normal file
|
@ -0,0 +1,71 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'bootstrap5/form.html' import render_form %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 position-relative main_pane">
|
||||
{% include "_home_nav.html" %}
|
||||
<div class="post_list">
|
||||
{% for post in posts %}
|
||||
{% include 'post/_post_teaser.html' %}
|
||||
{% else %}
|
||||
<p>{{ _('No posts yet.') }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<nav aria-label="Pagination" class="mt-4">
|
||||
{% if prev_url %}
|
||||
<a href="{{ prev_url }}" class="btn btn-primary" rel="nofollow">
|
||||
<span aria-hidden="true">←</span> {{ _('Previous page') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if next_url %}
|
||||
<a href="{{ next_url }}" class="btn btn-primary" rel="nofollow">
|
||||
{{ _('Next page') }} <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="get">
|
||||
<input type="search" name="search" class="form-control mt-2" placeholder="{{ _('Search') }}" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('Active communities') }}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for community in active_communities %}
|
||||
<li class="list-group-item">
|
||||
<a href="/c/{{ community.link() }}"><img src="{{ community.icon_image() }}" class="community_icon rounded-circle" loading="lazy" />
|
||||
{{ community.display_name() }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="mt-4"><a class="btn btn-primary" href="/communities">Explore communities</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('About %(site_name)s', site_name=g.site.name) }}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>{{ g.site.description|safe }}</strong></p>
|
||||
<p>{{ g.site.sidebar|safe }}</p>
|
||||
<p class="mt-4">
|
||||
<a class="no-underline" href="{{ rss_feed }}" rel="nofollow"><span class="fe fe-rss"></span> RSS feed</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
13
app/utils.py
13
app/utils.py
|
@ -454,3 +454,16 @@ def awaken_dormant_instance(instance):
|
|||
instance.gone_forever = True
|
||||
instance.dormant = True
|
||||
db.session.commit()
|
||||
|
||||
|
||||
epoch = datetime(1970, 1, 1)
|
||||
|
||||
def epoch_seconds(date):
|
||||
td = date - epoch
|
||||
return td.days * 86400 + td.seconds + (float(td.microseconds) / 1000000)
|
||||
|
||||
def post_ranking(score, date: datetime):
|
||||
order = math.log(max(abs(score), 1), 10)
|
||||
sign = 1 if score > 0 else -1 if score < 0 else 0
|
||||
seconds = epoch_seconds(date) - 1685766018
|
||||
return round(sign * order + seconds / 45000, 7)
|
||||
|
|
|
@ -16,7 +16,6 @@ class Config(object):
|
|||
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None
|
||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||
#SQLALCHEMY_ECHO = True # set to true to see debugging SQL in console
|
||||
RECAPTCHA3_PUBLIC_KEY = os.environ.get("RECAPTCHA3_PUBLIC_KEY")
|
||||
RECAPTCHA3_PRIVATE_KEY = os.environ.get("RECAPTCHA3_PRIVATE_KEY")
|
||||
MODE = os.environ.get('MODE') or 'development'
|
||||
|
|
Loading…
Reference in a new issue